import { isAppUser } from '@api/helpers/device.helper';
import {
  AUTH_COOKIE_NAME,
  authCookieOptions,
} from '@api/helpers/cookie.helper';
import {
  Body,
  Controller,
  Headers,
  Param,
  Patch,
  Post,
  Req,
  Res,
  UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { plainToInstance } from 'class-transformer';
import { Request, Response } from 'express';
import {
  FcmTokenDto,
  IsAvaliableLoginIdParam,
  IsAvaliableLoginIdResponseDto,
  IsTrialResponseDto,
  LookupLoginIdDto,
  LookupLoginIdResponseDto,
  LookupPasswordDto,
  RefreshTokenDto,
  ResetPasswordDto,
  SignInDto,
  SignInResponseDto,
  SignUpDto,
} from './auth.dto';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt.guard';
import { tokenMemId } from './token-member';

//* 비밀번호 재설정 티켓 전달 — 헤더 우선, 쿠키를 보내는 기기는 쿠키로도 건넌다(DD5)
const RESET_TICKET_HEADER = 'x-password-reset-ticket';
const RESET_TICKET_COOKIE = '_prT';
const RESET_TICKET_MAX_AGE = 1000 * 60 * 10; //* 10분

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

  @ApiOperation({ summary: '회원가입' })
  @Post('signup')
  async signup(@Body() dto: SignUpDto) {
    const isTrial = await this.authService.signup(dto);
    return plainToInstance(IsTrialResponseDto, {
      isTrial,
    });
  }

  @ApiOperation({ summary: '로그인' })
  @Post('signin')
  async signin(
    @Body() dto: SignInDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
    @Headers('user-agent') userAgent: string,
  ) {
    let isApp = isAppUser(userAgent);

    const { accessToken, fc, refreshToken } = await this.authService.signin(
      dto,
      isApp,
    );

    //* 옛 _aT 잔재 제거 후 새 토큰 set (옵션 통일 — 누적/만료토큰 우선읽힘 방지)
    res.clearCookie(AUTH_COOKIE_NAME, authCookieOptions(req));
    res.cookie(AUTH_COOKIE_NAME, accessToken, authCookieOptions(req, true));

    const response = {
      fc: {
        id: fc.id,
        loginId: fc.loginId,
        memType: fc.memType,
        name: fc.name,
        phone: fc.phone,
        email: fc.email,
        agency: fc.agency,
        myReferralCode: fc.myReferralCode,
        memCreatedAt: fc.memCreatedAt,
        memPushEvent: fc.memPushEvent,
        accessToken, // 브릿지로 전달하기 위해 추가
      },
      refreshToken,
    };

    return response; //* signin 응답에는 access·refresh 토큰이 실린다 — 로그로 내보내지 않는다(O2)
  }

  @ApiOperation({ summary: '아이디중복검사' })
  @Post(':loginId/avaliable')
  async isAvaliable(@Param() param: IsAvaliableLoginIdParam) {
    const doesExist = await this.authService.doesExist(param.loginId);
    return plainToInstance(IsAvaliableLoginIdResponseDto, {
      avaliable: !doesExist,
    });
  }

  @ApiOperation({ summary: '아이디찾기' })
  @Post('lookup-loginId')
  async lookupLoginId(@Body() dto: LookupLoginIdDto) {
    const loginId = await this.authService.lookupLoginId(dto);
    return plainToInstance(LookupLoginIdResponseDto, { loginId });
  }

  @ApiOperation({ summary: '비밀번호찾기' })
  @Post('lookup-password')
  async lookupPassword(
    @Body() dto: LookupPasswordDto,
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ) {
    const ticket = await this.authService.lookupPassword(dto);

    res.set('Cache-Control', 'private, no-store');
    res.cookie(RESET_TICKET_COOKIE, ticket, {
      ...authCookieOptions(req),
      maxAge: RESET_TICKET_MAX_AGE,
    });
    return { ticket };
  }

  @ApiOperation({ summary: '새비밀번호설정' })
  @Patch('reset-password')
  async resetPassword(
    @Body() dto: ResetPasswordDto,
    @Headers(RESET_TICKET_HEADER) header: string | undefined,
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ) {
    const ticket = header || req.cookies?.[RESET_TICKET_COOKIE]; //* 헤더 우선
    await this.authService.updatePassword(dto, ticket);

    //* 쓴 티켓은 같은 옵션으로 지운다(옵션이 다르면 지워지지 않는다)
    res.clearCookie(RESET_TICKET_COOKIE, authCookieOptions(req));
  }

  @ApiOperation({ summary: '로그아웃' })
  @Post('signout')
  async signout(
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
    @Headers('user-agent') userAgent: string,
  ) {
    //* 앱환경 구분을 위해 네이티브 세팅한 userAgent값 검증
    let isApp = userAgent.includes('insuniverse');

    //* 앱환경에서 로그아웃시 DB에 저장되어있는 refreshToken 공백처리
    if (isApp) {
      await this.authService.signout(req);
    }

    //* set 과 동일 옵션으로 삭제해야 실제로 지워진다(옵션 불일치 시 옛 쿠키 잔존)
    res.clearCookie(AUTH_COOKIE_NAME, authCookieOptions(req));
    return;
  }

  @ApiOperation({ summary: 'FCM 토큰 저장' })
  @UseGuards(JwtAuthGuard)
  @Post('fcm-token')
  async fcmToken(@Body() dto: FcmTokenDto, @Req() req: Request) {
    //* 대상 회원은 토큰으로만 정한다 — body loginId 로 남의 푸시 토큰을 덮어쓰지 못한다
    await this.authService.updateFcmToken(tokenMemId(req), dto);
  }

  @ApiOperation({ summary: '앱 자동로그인을 위한 refresh token 처리' })
  @Post('refresh')
  async refresh(
    @Body() dto: RefreshTokenDto,
    @Res({ passthrough: true }) res: Response,
    @Req() req: Request,
  ) {
    const { accessToken, fc, refreshToken } =
      await this.authService.refresh(dto);

    //* 옛 _aT 잔재 제거 후 새 토큰 set (옵션 통일)
    res.clearCookie(AUTH_COOKIE_NAME, authCookieOptions(req));
    res.cookie(AUTH_COOKIE_NAME, accessToken, authCookieOptions(req, true));

    return {
      fc: {
        id: fc.id,
        loginId: fc.loginId,
        memType: fc.memType,
        name: fc.name,
        phone: fc.phone,
        email: fc.email,
        agency: fc.agency,
        myReferralCode: fc.myReferralCode,
        memCreatedAt: fc.memCreatedAt,
        memPushEvent: fc.memPushEvent,
        accessToken, // 브릿지로 전달하기 위해 추가
      },
      refreshToken,
    };
  }
}
