import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ZodResponse } from 'nestjs-zod';
import type { AccessTokenPayload } from '@pac/types';
import { Public } from '../../common/decorators/public.decorator';
import { CurrentUser } from '../../common/decorators/current-user.decorator';
import {
  TenantScope,
  TenantScopeContext,
} from '../../common/decorators/tenant-scope.decorator';
import { AuthService } from './auth.service';
import {
  ClientDiagRequestDto,
  ExchangeCodeRequestDto,
  ExchangeCodeResponseDto,
  MockLoginRequestDto,
  MockLoginResponseDto,
  MockUsersResponseDto,
  RefreshTokenRequestDto,
  RefreshTokenResponseDto,
  SessionResponseDto,
  TokenExchangeRequestDto,
  TokenExchangeResponseDto,
} from './dto/auth.dto';

@ApiTags('auth')
@Controller('auth')
export class AuthController {
  constructor(private readonly auth: AuthService) {}

  @Public()
  @Post('token')
  @ZodResponse({ status: 200, type: TokenExchangeResponseDto })
  @ApiOperation({
    summary:
      'Token Exchange — host backend trades appId+appSecret+user for an accessToken + one-time code',
  })
  exchangeToken(@Body() dto: TokenExchangeRequestDto) {
    return this.auth.exchangeToken(dto);
  }

  @Public()
  @Post('exchange-code')
  @ZodResponse({ status: 200, type: ExchangeCodeResponseDto })
  @ApiOperation({ summary: 'Exchange a one-time code (from iframe URL) for the accessToken' })
  exchangeCode(@Body() dto: ExchangeCodeRequestDto) {
    return this.auth.exchangeCode(dto.code);
  }

  @Public()
  @Post('refresh')
  @ZodResponse({ status: 200, type: RefreshTokenResponseDto })
  @ApiOperation({ summary: 'Refresh the accessToken before expiry' })
  refresh(@Body() dto: RefreshTokenRequestDto) {
    return this.auth.refresh(dto.refreshToken);
  }

  @Public()
  @Post('client-diag')
  @ApiOperation({
    summary:
      '前端 bootstrap 诊断信标 — 把嵌入首开鉴权走的分支(有无 code / store 有无 token / 结局)落一行服务端日志,定位「登录已过期」',
  })
  clientDiag(@Body() dto: ClientDiagRequestDto) {
    this.auth.clientDiag(dto);
    return { ok: true };
  }

  @Public()
  @Post('mock-login')
  @ZodResponse({ status: 200, type: MockLoginResponseDto })
  @ApiOperation({
    summary:
      'Mock login(开发 / 试部署用,env 门控)— 按 { tenant, role } 预制 user payload 直产 JWT',
    description:
      'env: PAC_ENABLE_MOCK_LOGIN=true 强开 / =false 强关;不设按 NODE_ENV(prod 默认关)。' +
      '生产真 host SSO 接入后,把 env 设 false 或删该 endpoint。',
  })
  mockLogin(@Body() dto: MockLoginRequestDto) {
    return this.auth.mockLogin(dto);
  }

  @Public()
  @Get('mock-users')
  @ZodResponse({ status: 200, type: MockUsersResponseDto })
  @ApiOperation({
    summary: '客服花名册(开发 / 试部署用)— 给"快速登录"对话框选人',
    description:
      '派生自 data/jvs-dw/users.json(回访表 · 近 12 月在岗)。mock 登录禁用时返空数组。',
  })
  mockUsers() {
    return { users: this.auth.listMockUsers() };
  }

  @Public()
  @Get('mock-orgs')
  @ApiOperation({ summary: '(dev) 真实组织结构(品牌→诊所),给快速登录选数据范围' })
  async mockOrgs(@Query('host') host?: string, @Query('tenant') tenant?: string) {
    // 默认 friday(多品牌宿主才需要选品牌/诊所);jvs-dw 走客服花名册路径
    return this.auth.listMockOrgs(host || 'friday', tenant || 'friday-market');
  }

  @Get('session')
  @ApiBearerAuth('accessToken')
  @ZodResponse({ status: 200, type: SessionResponseDto })
  @ApiOperation({
    summary:
      '当前会话视图 — 把 JWT.orgScope 展开成 clinicIds/sourceUnits 返给前端(设计 B:JWT 只装通用 orgScope,前端展不开)',
  })
  async session(
    @CurrentUser() user: AccessTokenPayload,
    @TenantScope() scope: TenantScopeContext,
  ) {
    // 诊所名字典:服务端派生(host.clinicNames)打底,宿主换票传的 dictionary.clinics 覆盖优先。
    // 这样前端显示诊所名不再依赖每次登录都传全 —— 服务端补齐 GUID→中文名,登录传的仅作覆盖。
    const serverClinicNames = await this.auth.getClinicNames(user.hostId);
    const loginDict = user.dictionary;
    const mergedClinics = { ...serverClinicNames, ...(loginDict?.clinics ?? {}) };
    const dictionary =
      Object.keys(mergedClinics).length > 0 || loginDict?.users
        ? {
            ...(Object.keys(mergedClinics).length > 0 ? { clinics: mergedClinics } : {}),
            ...(loginDict?.users ? { users: loginDict.users } : {}),
          }
        : undefined;
    return {
      sub: user.sub,
      hostId: user.hostId,
      tenantId: user.tenantId,
      role: user.role,
      // ⭐ 按 role 现算,不回传 JWT 里那份快照 —— 与 PermissionsGuard 同源(见那里的长注释)。
      //   前端 auth-store.loadSession 会 merge 这个字段,所以发版新增权限后**不必重登**:
      //   下一次 loadSession 就把新权限带回来,与服务端判定口径一致。
      //   回传 user.permissions 则前后端会一起停在旧清单上,merge 也救不了。
      permissions: this.auth.resolvePermissions(user.role),
      orgScope: user.orgScope,
      clinicIds: scope.clinicIds, // 拦截器已按 host org 树展开
      sourceUnits: scope.sourceUnits,
      ...(dictionary ? { dictionary } : {}),
      // 宿主动作跳转模板 —— 前端"新建预约"等按钮据此打开宿主页(缺失=不渲染/不跳)
      actionUrls: await this.auth.getActionUrls(user.hostId),
    };
  }
}
