import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Post,
  Query,
  Req,
  Res,
  UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
import { AuthGuard } from '@nestjs/passport';
import { Throttle } from '@nestjs/throttler';
import type { CookieOptions, Request, Response } from 'express';
import { AuthService, type SignupResult } from './auth.service';
import { SignupDto } from './dto/signup.dto';
import { LoginDto } from './dto/login.dto';
import { ResendVerificationDto } from './dto/resend-verification.dto';
import { ForgotPasswordDto } from './dto/forgot-password.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { CurrentUser } from './decorators/current-user.decorator';
import { SkipTransform } from '../../common/decorators/skip-transform.decorator';
import type { PublicUser } from '../user/user.service';

// 쿠키 만료 시간(ms)
const ACCESS_MAX_AGE = 15 * 60 * 1000; // 15분
const REFRESH_MAX_AGE = 14 * 24 * 60 * 60 * 1000; // 14일
// refresh 쿠키 전송 경로 — /auth/* 요청에만 첨부 (글로벌 prefix 'api' 포함)
const REFRESH_COOKIE_PATH = '/api/auth';

// 인증 컨트롤러 — 라우팅/쿠키 발급만 담당, 비즈니스 로직은 AuthService 위임
@ApiTags('Auth')
@Controller('auth')
export class AuthController {
  constructor(
    private readonly authService: AuthService,
    private readonly config: ConfigService,
  ) {}

  // 프론트엔드(웹) 기준 URL — 소셜 로그인/이메일 인증 후 리다이렉트 대상
  private webUrl(): string {
    const url =
      this.config.get<string>('FRONTEND_URL') ||
      this.config.get<string>('CORS_ORIGIN') ||
      'http://localhost:5273';
    return url.split(',')[0].trim().replace(/\/$/, '');
  }

  // HttpOnly·Secure 쿠키 공통 옵션
  private cookieBase(): CookieOptions {
    const isProd = this.config.get<string>('NODE_ENV') === 'production';
    return {
      httpOnly: true,
      secure: isProd, // 운영: HTTPS 전용
      sameSite: isProd ? 'none' : 'lax', // 운영: 크로스 도메인 허용
    };
  }

  // access/refresh 토큰을 쿠키로 설정
  private setAuthCookies(
    res: Response,
    accessToken: string,
    refreshToken: string,
  ): void {
    res.cookie('access_token', accessToken, {
      ...this.cookieBase(),
      path: '/',
      maxAge: ACCESS_MAX_AGE,
    });
    res.cookie('refresh_token', refreshToken, {
      ...this.cookieBase(),
      path: REFRESH_COOKIE_PATH,
      maxAge: REFRESH_MAX_AGE,
    });
  }

  // 인증 쿠키 제거 (로그아웃)
  private clearAuthCookies(res: Response): void {
    res.clearCookie('access_token', { ...this.cookieBase(), path: '/' });
    res.clearCookie('refresh_token', {
      ...this.cookieBase(),
      path: REFRESH_COOKIE_PATH,
    });
  }

  // PublicUser 로 새 세션 토큰 발급 + 쿠키 설정 (로그인/소셜 공통)
  private async loginWith(res: Response, user: PublicUser): Promise<void> {
    const tokens = await this.authService.issueTokensForNewSession(user);
    this.setAuthCookies(res, tokens.accessToken, tokens.refreshToken);
  }

  @Post('signup')
  @HttpCode(HttpStatus.CREATED)
  // 가입 남용/메일 폭탄 방지 — 운영 권장 5회/분 (현재 테스트용 1000회로 완화)
  @Throttle({ default: { limit: 1000, ttl: 60_000 } })
  @ApiOperation({ summary: '회원가입 (인증 메일 발송, 자동 로그인 안 함)' })
  @ApiResponse({ status: 201, description: '가입 접수 — 인증 메일 발송됨' })
  @ApiResponse({ status: 409, description: '이미 가입된 이메일 (BIZ_001)' })
  signup(@Body() dto: SignupDto): Promise<SignupResult> {
    // 이메일 인증 완료 전까지 로그인 불가 — 쿠키를 발급하지 않는다
    return this.authService.signup(dto);
  }

  @Post('resend-verification')
  @HttpCode(HttpStatus.OK)
  // 메일 폭탄 방지 — 운영 권장 3회/분 (현재 테스트용 1000회로 완화)
  @Throttle({ default: { limit: 1000, ttl: 60_000 } })
  @ApiOperation({ summary: '인증 메일 재발송' })
  @ApiResponse({ status: 200, description: '재발송 접수(존재 여부 비노출)' })
  async resend(@Body() dto: ResendVerificationDto): Promise<null> {
    await this.authService.resendVerification(dto.email);
    return null;
  }

  @Post('forgot-password')
  @HttpCode(HttpStatus.OK)
  // 메일 폭탄/계정 탐색 방지 — 운영 권장 3회/분 (현재 테스트용 1000회로 완화)
  @Throttle({ default: { limit: 1000, ttl: 60_000 } })
  @ApiOperation({ summary: '비밀번호 재설정 요청 (재설정 메일 발송)' })
  @ApiResponse({ status: 200, description: '요청 접수(존재 여부 비노출)' })
  async forgotPassword(@Body() dto: ForgotPasswordDto): Promise<null> {
    await this.authService.requestPasswordReset(dto.email);
    return null;
  }

  @Post('reset-password')
  @HttpCode(HttpStatus.OK)
  // 토큰 추측 방지 — 운영 권장 5회/분 (현재 테스트용 1000회로 완화)
  @Throttle({ default: { limit: 1000, ttl: 60_000 } })
  @ApiOperation({ summary: '비밀번호 재설정 (토큰 + 새 비밀번호)' })
  @ApiResponse({ status: 200, description: '비밀번호 변경 성공' })
  @ApiResponse({ status: 400, description: '토큰 만료/무효 (BIZ_001)' })
  async resetPassword(@Body() dto: ResetPasswordDto): Promise<null> {
    await this.authService.resetPassword(dto.token, dto.password);
    return null;
  }

  // 이메일 인증 링크 — 메일의 버튼에서 직접 GET 진입(보통 새 탭).
  // 검증 후 결과 페이지로 리다이렉트하며, 그 페이지는 완료를 표시하고 창을 닫는다.
  @Get('verify-email')
  @SkipTransform()
  @ApiOperation({ summary: '이메일 인증 (메일 링크)' })
  async verifyEmail(
    @Query('token') token: string,
    @Res() res: Response,
  ): Promise<void> {
    try {
      await this.authService.verifyEmail(token);
      res.redirect(`${this.webUrl()}/email-verified?ok=1`);
    } catch {
      res.redirect(`${this.webUrl()}/email-verified?ok=0`);
    }
  }

  @Post('login')
  @HttpCode(HttpStatus.OK)
  // 무차별 대입 방지 — 운영 권장 10회/분 (현재 테스트용 1000회로 완화)
  @Throttle({ default: { limit: 1000, ttl: 60_000 } })
  @ApiOperation({ summary: '로그인' })
  @ApiResponse({ status: 200, description: '로그인 성공, 인증 쿠키 발급' })
  @ApiResponse({
    status: 401,
    description: '이메일/비밀번호 불일치 (BIZ_001)',
  })
  async login(
    @Body() dto: LoginDto,
    @Res({ passthrough: true }) res: Response,
  ): Promise<PublicUser> {
    const user = await this.authService.validateUser(dto);
    await this.loginWith(res, user);
    return user;
  }

  // ── 구글 OAuth ──
  @Get('google')
  @SkipTransform()
  @UseGuards(AuthGuard('google'))
  @ApiOperation({ summary: '구글 로그인 시작 (구글 동의 화면으로 이동)' })
  googleAuth(): void {
    // 가드(AuthGuard)가 구글 인증 페이지로 리다이렉트 — 본문 없음
  }

  @Get('google/callback')
  @SkipTransform()
  @UseGuards(AuthGuard('google'))
  @ApiOperation({ summary: '구글 로그인 콜백' })
  async googleCallback(
    @Req() req: Request,
    @Res() res: Response,
  ): Promise<void> {
    await this.loginWith(res, req.user as PublicUser);
    res.redirect(`${this.webUrl()}/projects`);
  }

  // ── 카카오 OAuth ──
  @Get('kakao')
  @SkipTransform()
  @UseGuards(AuthGuard('kakao'))
  @ApiOperation({ summary: '카카오 로그인 시작 (카카오 동의 화면으로 이동)' })
  kakaoAuth(): void {
    // 가드(AuthGuard)가 카카오 인증 페이지로 리다이렉트 — 본문 없음
  }

  @Get('kakao/callback')
  @SkipTransform()
  @UseGuards(AuthGuard('kakao'))
  @ApiOperation({ summary: '카카오 로그인 콜백' })
  async kakaoCallback(
    @Req() req: Request,
    @Res() res: Response,
  ): Promise<void> {
    await this.loginWith(res, req.user as PublicUser);
    res.redirect(`${this.webUrl()}/projects`);
  }

  @Post('logout')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: '로그아웃 (세션 폐기 + 인증 쿠키 제거)' })
  @ApiResponse({ status: 200, description: '로그아웃 성공' })
  async logout(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<null> {
    // 이 세션(패밀리)을 서버측에서 폐기 — 이후 해당 refresh 토큰 재사용 차단
    const cookies = req.cookies as Record<string, string> | undefined;
    await this.authService.revokeSession(cookies?.refresh_token);
    this.clearAuthCookies(res);
    return null;
  }

  @Post('refresh')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'access/refresh 토큰 회전 (refresh 쿠키 필요)' })
  @ApiResponse({ status: 200, description: '토큰 회전 성공' })
  @ApiResponse({
    status: 401,
    description: 'refresh 토큰 만료/누락/재사용 (AUTH_001)',
  })
  async refresh(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<null> {
    const cookies = req.cookies as Record<string, string> | undefined;
    try {
      // 회전형 refresh — 재사용 탐지 시 401(AUTH_001), 정상 시 새 토큰 쌍으로 교체
      const tokens = await this.authService.rotateTokens(
        cookies?.refresh_token,
      );
      this.setAuthCookies(res, tokens.accessToken, tokens.refreshToken);
      return null;
    } catch (e) {
      // 폐기/무효 토큰이면 더 이상 보내지 않도록 쿠키 제거
      this.clearAuthCookies(res);
      throw e;
    }
  }

  @Get('me')
  @UseGuards(JwtAuthGuard)
  @ApiOperation({ summary: '현재 로그인 사용자 조회 (부트스트랩/가드용)' })
  @ApiResponse({ status: 200, description: '사용자 조회 성공' })
  @ApiResponse({ status: 401, description: '미인증 (AUTH_001)' })
  me(@CurrentUser() user: PublicUser): PublicUser {
    return user;
  }
}
