import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ZodResponse } from 'nestjs-zod';
import { Permission } from '@pac/types';
import { RequirePermission } from '../../common/decorators/permissions.decorator';
import { TenantScope } from '../../common/decorators/tenant-scope.decorator';
import type { TenantScopeContext } from '../../common/decorators/tenant-scope.decorator';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import type { AuthenticatedUser } from '../../common/decorators/current-user.decorator';
import { PlanAssignmentService } from './plan-assignment.service';
import { AgentRosterService } from './agent-roster.service';
import {
  CreateAssignmentRequestDto,
  CreateAssignmentResponseDto,
  ListAssignmentsResponseDto,
  AssignmentDetailResponseDto,
  ListAgentsResponseDto,
  RevokeAssignmentResponseDto,
} from './dto/plan-assignment.dto';

/**
 * 批次分配端点 —— 门诊经理专用。
 *
 * ⛔ **刻意不改** `POST /plans/:id/assign` 与 `/plans/:id/recycle`:
 *   前者维持 PLAN_ASSIGN(认领语义,staff 也有,前端虽撤了入口但后端保留 —— T12/T16),
 *   后者维持 PLAN_RECYCLE(退回是客服路径)。
 *   把批次能力塞进那两个端点会让"客服自助"与"主管派单"两条路的权限纠缠在一起。
 *
 * 路由挂在 `plans/assignments` 而不是顶层 `plan-assignments`:
 *   它是 plan 的从属资源,跟 `plans/:id/executions` 同一层级心智。
 */
@ApiTags('plan-assignments')
@ApiBearerAuth('accessToken')
@Controller('plans/assignments')
export class AssignmentController {
  constructor(
    private readonly assignments: PlanAssignmentService,
    private readonly roster: AgentRosterService,
  ) {}

  /**
   * 客服名册 + 在手负载 —— **一个端点**,不拆成两个。
   * 分配问「还能吃多少」、跟踪问「压了多少」,是同一份数据的两种读法;
   * 拆开必然口径漂移(一个算 assigned、一个算 assigned+active),而漂了不报错。
   *
   * 完整路径 **`GET /pac/v1/plans/assignments/agents`**(控制器前缀是 `plans/assignments`)。
   * ⚠️ 别写成 `/plans/agents` —— 那个路径归 PlanController,会被它的裸 `@Get(':id')`
   *   当成 planId='agents',报出来是 Prisma 的 uuid 解析错,看不出是路由问题(实测踩过)。
   * ⚠️ 本方法必须声明在下面 `@Get(':id')` **之前**:同一控制器内 Nest 按声明顺序匹配,
   *   放后面 'agents' 会被当成 assignmentId。
   */
  @Get('agents')
  @RequirePermission(Permission.PLAN_DISPATCH)
  @ZodResponse({ status: 200, type: ListAgentsResponseDto })
  @ApiOperation({ summary: '在岗客服名册 + 在手负载(近 N 月有回访记录者;名册外亦可指定)' })
  async agents(
    @TenantScope() scope: TenantScopeContext,
    @Query('clinicId') clinicId: string,
    @Query('months') months?: string,
    @Query('include') include?: string,
  ) {
    return this.roster.list(scope, clinicId, {
      months: months ? Number(months) : undefined,
      // 主管显式点名的人即使不在名册也要能查到 —— 名册是建议不是白名单
      extraUserIds: include ? include.split(',').filter(Boolean) : undefined,
    });
  }

  @Post()
  @RequirePermission(Permission.PLAN_DISPATCH)
  @ZodResponse({ status: 201, type: CreateAssignmentResponseDto })
  @ApiOperation({
    summary: '确认分配 —— 主管在确认单上点确认时调用(整条生产线唯一的写动作)',
    description:
      '幂等:requestId 由服务端在生成确认单时铸造,重复提交返回同一批次 + duplicate:true。' +
      '部分计划在确认期间被他人认领时其余照落,逐条在 skipped 里说明原因;一条都落不上则不建批次。',
  })
  async create(
    @TenantScope() scope: TenantScopeContext,
    @CurrentUser() user: AuthenticatedUser,
    @Body() dto: CreateAssignmentRequestDto,
  ) {
    return this.assignments.create(
      scope,
      { userId: user.sub, permissions: user.permissions },
      dto,
    );
  }

  @Get()
  @RequirePermission(Permission.PLAN_DISPATCH)
  @ZodResponse({ status: 200, type: ListAssignmentsResponseDto })
  @ApiOperation({ summary: '我分的那些批 —— 批次列表 + 每批汇总(分了/在手/已退回/涉及几人)' })
  async list(
    @TenantScope() scope: TenantScopeContext,
    @CurrentUser() user: AuthenticatedUser,
    @Query('mine') mine?: string,
  ) {
    // mine=1 → 只看自己发起的。默认看本 scope 全部(leader 之间要能互相看见,
    // 否则"这个诊所这周分了多少"永远拼不出来)
    return this.assignments.list(scope, mine === '1' ? user.sub : undefined);
  }

  @Post(':id/revoke')
  @RequirePermission(Permission.PLAN_DISPATCH)
  @ZodResponse({ status: 200, type: RevokeAssignmentResponseDto })
  @ApiOperation({
    summary: '撤销整批(限时)—— 分错人 / 条件填错时的补救',
    description:
      '⚠️ 撤销 ≠ 退回:这是**主管收回整批**,不是客服退单条。' +
      '已被客服打开过(view 事件)的单**不收**并如实回报 —— 他可能已经联系了患者。' +
      '超出撤销窗口后请走逐条退回(那条路会留下原因)。',
  })
  async revoke(
    @TenantScope() scope: TenantScopeContext,
    @CurrentUser() user: AuthenticatedUser,
    @Param('id') id: string,
  ) {
    return this.assignments.revoke(scope, { userId: user.sub, permissions: user.permissions }, id);
  }

  @Get(':id')
  @RequirePermission(Permission.PLAN_DISPATCH)
  @ZodResponse({ status: 200, type: AssignmentDetailResponseDto })
  @ApiOperation({ summary: '单批全貌 —— 按客服拆 + 退回原因分布' })
  async detail(@TenantScope() scope: TenantScopeContext, @Param('id') id: string) {
    return this.assignments.detail(scope, id);
  }
}
