import {
  ADMIN_COOKIE_NAME,
  ADMIN_PENDING_COOKIE_NAME,
} from '@api/helpers/admin-transport.constants';
import { Body, Controller, Post, Req, Res, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { plainToInstance } from 'class-transformer';
import { Request, Response } from 'express';
import { requestContext } from '../../audit/audit-actor';
import type { AdminKind } from '../principal/principal';
import { AdminSigninRateLimitGuard } from './signin-rate-limit.guard';
import { OriginCheckGuard } from './origin-check.guard';
import {
  AdminSignInDto,
  ChangeSigninPasswordDto,
  CorpOrgSignInDto,
  CorpSignInDto,
  CorpSignInResponseDto,
  SalesSignInDto,
  SalesSignInResponseDto,
  VerifySigninCodeDto,
} from './auth.dto';
import { AdminAuthService, type SigninResult } from './auth.service';
import {
  clearPendingCookie,
  setAdminCookie,
  setPendingCookie,
} from './admin-session';

//* 로그인 응답 본문 — 네 로그인과 코드 · 비밀번호 단계가 같은 모양을 낸다(계획 3.15). 화면은 res.data.admin 을 그대로 쓴다.
function signinResponse(kind: AdminKind, admin: unknown) {
  if (kind === 'admin') return { admin };
  if (kind === 'sales') return plainToInstance(SalesSignInResponseDto, { admin });
  return plainToInstance(CorpSignInResponseDto, { admin });
}

//* 세션이면 _aaT · 본문, 대기면 _aaP · { result }(계획 4.2). 대기 중에는 같은 브라우저의 _aaT 를 쓰지도 지우지도 않는다(7.1-4).
function respond(res: Response, kind: AdminKind, r: SigninResult) {
  if (r.result) {
    setPendingCookie(res, r.pendingValue!);
    return { result: r.result };
  }
  clearPendingCookie(res);
  setAdminCookie(res, r.accessToken!);
  return signinResponse(kind, r.admin);
}

//* 레이트리밋(AdminSigninRateLimitGuard)은 로그인 넷에만 메서드 단위로 건다(SD-4). 로그아웃에 걸면 제한에 걸린
//*  주소의 로그아웃이 429 로 막혀 세션을 못 끊는다. 기본 OFF(`RATE_LIMIT_ADMIN_SIGNIN_PER_MIN=0`) —
//*  M7 이 실패 로그인마다 더한 감사 트랜잭션의 증폭을 앞에서 막는 마개다. 코드 · 비밀번호 단계는 계정 단위 실패 수가 막는다.
@ApiTags('[관리자] 인증')
@Controller('admin/auth')
export class AdminAuthController {
  constructor(private adminAuthService: AdminAuthService) {}

  @ApiOperation({ summary: '관리자 로그인' })
  @Post('signin')
  @UseGuards(AdminSigninRateLimitGuard)
  async signin(
    @Body() dto: AdminSignInDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.signin(dto, requestContext(req));
    return respond(res, 'admin', r);
  }

  @ApiOperation({ summary: '관리자 로그아웃' })
  @Post('signout')
  async signout(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ) {
    //* 쿠키를 먼저 지운다 — 세션 끊기가 던져 500 이어도 Set-Cookie 는 그 응답에 실린다.
    res.clearCookie(ADMIN_COOKIE_NAME);
    clearPendingCookie(res);
    await this.adminAuthService.signout(req.cookies, requestContext(req));
    return;
  }

  //* 로그인 코드 단계 — 대기 쿠키만 있고 판정 신원이 없다(예외 원장). 출처 검사는 미들웨어와 겹치지만 결정대로 둔다(7.1-7).
  @ApiOperation({ summary: '관리자 로그인 2차 인증 코드 확인' })
  @Post('2fa/verify')
  @UseGuards(OriginCheckGuard)
  async verifyCode(
    @Body() dto: VerifySigninCodeDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.verifyCode(
      req.cookies?.[ADMIN_PENDING_COOKIE_NAME],
      dto.code,
      requestContext(req),
    );
    return respond(res, r.kind, r);
  }

  //* 로그인 비밀번호 변경 단계(D4 안 1) — 남이 정한 비밀번호로 들어온 로그인이 새 비밀번호를 낸다.
  @ApiOperation({ summary: '관리자 로그인 비밀번호 변경' })
  @Post('password')
  @UseGuards(OriginCheckGuard)
  async changePassword(
    @Body() dto: ChangeSigninPasswordDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.changePassword(
      req.cookies?.[ADMIN_PENDING_COOKIE_NAME],
      dto.newPassword,
      requestContext(req),
    );
    return respond(res, r.kind, r);
  }

  //* 영업대리점 로그인
  @ApiOperation({ summary: '영업대리점 관리자 로그인' })
  @Post('sales/signin')
  @UseGuards(AdminSigninRateLimitGuard)
  async salesSignin(
    @Body() dto: SalesSignInDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.salesSignin(dto, requestContext(req));
    return respond(res, 'sales', r);
  }

  //* 기업관리자 로그인
  @ApiOperation({ summary: '기업관리자 로그인' })
  @Post('corp/signin')
  @UseGuards(AdminSigninRateLimitGuard)
  async corpSignin(
    @Body() dto: CorpSignInDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.corpSignin(dto, requestContext(req));
    return respond(res, 'corp-super', r);
  }

  //* 조직관리자 로그인 (tn_member 기반 CORP_ORG, 자기 부분트리 운영 가능)
  @ApiOperation({ summary: '조직관리자 로그인' })
  @Post('corp/org/signin')
  @UseGuards(AdminSigninRateLimitGuard)
  async corpOrgSignin(
    @Body() dto: CorpOrgSignInDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const r = await this.adminAuthService.corpOrgSignin(
      dto,
      requestContext(req),
    );
    return respond(res, 'org-manager', r);
  }
}
