import {
  Body,
  Controller,
  Get,
  HttpCode,
  HttpStatus,
  Post,
  Req,
  Res,
  UnauthorizedException,
  UseGuards,
} from '@nestjs/common';
import { Throttle } from '@nestjs/throttler';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './guards/jwt-auth.guard';
import { LoginDto } from './dto/login.dto';
import { RegisterDto } from './dto/register.dto';
import { LoginResponseDto, UserPublicDto } from './dto/auth-response.dto';
import type { AuthenticatedUser } from './strategies/jwt.strategy';

const ACCESS_TOKEN_COOKIE = 'ir_at';
const REFRESH_TOKEN_COOKIE = 'ir_rt';
const isProd = process.env.NODE_ENV === 'production';

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

  @Post('register')
  @HttpCode(HttpStatus.CREATED)
  @Throttle({ default: { limit: 5, ttl: 60_000 } })
  @ApiOperation({ summary: 'Register the first admin user (bootstrap only)' })
  @ApiResponse({ status: 201, type: UserPublicDto })
  @ApiResponse({ status: 409, description: 'Username already exists' })
  @ApiResponse({ status: 403, description: 'Registration is disabled once a user exists' })
  register(@Body() dto: RegisterDto): Promise<UserPublicDto> {
    return this.authService.register(dto) as unknown as Promise<UserPublicDto>;
  }

  @Post('login')
  @HttpCode(HttpStatus.OK)
  @Throttle({ default: { limit: 5, ttl: 60_000 } })
  @ApiOperation({ summary: 'Login and obtain access + refresh tokens' })
  @ApiResponse({ status: 200, type: LoginResponseDto })
  @ApiResponse({ status: 401, description: 'Invalid credentials' })
  async login(
    @Body() dto: LoginDto,
    @Res({ passthrough: true }) res: Response,
  ): Promise<LoginResponseDto> {
    const result = await this.authService.login(dto);
    // HttpOnly cookies are the primary session channel for the admin SPA
    // (XSS cannot read them). The access token is also returned in the
    // body for non-browser API clients.
    res.cookie(ACCESS_TOKEN_COOKIE, result.accessToken, {
      httpOnly: true,
      sameSite: 'lax',
      secure: isProd,
      path: '/',
    });
    res.cookie(REFRESH_TOKEN_COOKIE, result.refreshToken, {
      httpOnly: true,
      sameSite: 'lax',
      secure: isProd,
      // Only ever sent to /auth/refresh and /auth/logout
      path: '/auth',
    });
    // The refresh token deliberately stays HttpOnly-only.
    return {
      accessToken: result.accessToken,
      user: result.user,
    } as unknown as LoginResponseDto;
  }

  @Post('refresh')
  @HttpCode(HttpStatus.OK)
  @Throttle({ default: { limit: 10, ttl: 60_000 } })
  @ApiOperation({ summary: 'Rotate the refresh token cookie' })
  @ApiResponse({ status: 200, type: LoginResponseDto })
  @ApiResponse({ status: 401, description: 'Invalid refresh token' })
  async refresh(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
  ): Promise<LoginResponseDto> {
    const token = req.cookies?.[REFRESH_TOKEN_COOKIE];
    if (!token) {
      res.clearCookie(REFRESH_TOKEN_COOKIE, { path: '/auth' });
      throw new UnauthorizedException('Missing refresh token');
    }
    const result = await this.authService.refresh(token);
    res.cookie(ACCESS_TOKEN_COOKIE, result.accessToken, {
      httpOnly: true,
      sameSite: 'lax',
      secure: isProd,
      path: '/',
    });
    res.cookie(REFRESH_TOKEN_COOKIE, result.refreshToken, {
      httpOnly: true,
      sameSite: 'lax',
      secure: isProd,
      path: '/auth',
    });
    return {
      accessToken: result.accessToken,
      user: result.user,
    } as unknown as LoginResponseDto;
  }

  @Get('me')
  @UseGuards(JwtAuthGuard)
  @ApiOperation({ summary: 'Current authenticated user' })
  @ApiResponse({ status: 200, type: UserPublicDto })
  me(@Req() req: Request & { user: AuthenticatedUser }): Promise<UserPublicDto> {
    return this.authService.me(req.user.id) as unknown as Promise<UserPublicDto>;
  }

  @Post('logout')
  @HttpCode(HttpStatus.OK)
  @UseGuards(JwtAuthGuard)
  @ApiOperation({ summary: 'Revoke all tokens of the current user' })
  async logout(
    @Req() req: Request & { user: AuthenticatedUser },
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ success: true }> {
    await this.authService.logout(req.user.id);
    res.clearCookie(ACCESS_TOKEN_COOKIE, { path: '/' });
    res.clearCookie(REFRESH_TOKEN_COOKIE, { path: '/auth' });
    return { success: true };
  }
}
