import {
  BadRequestException,
  Body,
  Controller,
  HttpCode,
  HttpStatus,
  Post,
  Query,
  Req,
  Res,
  UseGuards,
} from '@nestjs/common';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { SkipThrottle, Throttle } from '@nestjs/throttler';
import type { FastifyReply, FastifyRequest } from 'fastify';

import {
  forgotPasswordRequestSchema,
  loginRequestSchema,
  registerRequestSchema,
  resetPasswordRequestSchema,
  twoFactorConfirmRequestSchema,
  twoFactorDisableRequestSchema,
  twoFactorVerifyRequestSchema,
  verifyEmailRequestSchema,
} from '@hexahost/contracts';

import { ZodValidationPipe } from '../common/pipes/zod-validation.pipe';

import { AuthService } from './auth.service';
import { CurrentUser, Public } from './decorators/current-user.decorator';
import { SessionGuard } from './guards/session.guard';
import { SessionService } from './session.service';

@ApiTags('auth')
@Controller('auth')
@SkipThrottle()
@Throttle({ default: { limit: 5, ttl: 60_000 } })
export class AuthController {
  constructor(
    private readonly authService: AuthService,
    private readonly sessionService: SessionService,
  ) {}

  @Public()
  @Post('register')
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({ summary: 'Register a new account' })
  @ApiResponse({ status: 201, description: 'Account created' })
  register(
    @Body(new ZodValidationPipe(registerRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
  ) {
    return this.authService.register(
      body as Parameters<AuthService['register']>[0],
      request.ip,
    );
  }

  @Public()
  @Post('login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Login with email and password' })
  login(
    @Body(new ZodValidationPipe(loginRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) reply: FastifyReply,
  ) {
    return this.authService.login(
      body as Parameters<AuthService['login']>[0],
      reply,
      request.ip,
      request.headers['user-agent'],
    );
  }

  @Public()
  @Post('logout')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Logout current session' })
  logout(
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) reply: FastifyReply,
  ) {
    const token = this.sessionService.readSessionToken(request);
    return this.authService.logout(token, reply, request.ip);
  }

  @Public()
  @Post('verify-email')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Verify email address' })
  verifyEmail(
    @Body(new ZodValidationPipe(verifyEmailRequestSchema)) body: unknown,
    @Query('token') queryToken: string | undefined,
    @Req() request: FastifyRequest,
  ) {
    const payload = body as { token?: string };
    const token = payload.token ?? queryToken;

    if (!token) {
      throw new BadRequestException('Verification token is required');
    }

    return this.authService.verifyEmail({ token }, request.ip);
  }

  @UseGuards(SessionGuard)
  @Post('resend-verification')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Resend email verification' })
  resendVerification(
    @CurrentUser() user: { id: string },
    @Req() request: FastifyRequest,
  ) {
    return this.authService.resendVerification(user.id, request.ip);
  }

  @Public()
  @Post('forgot-password')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Request password reset email' })
  forgotPassword(
    @Body(new ZodValidationPipe(forgotPasswordRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
  ) {
    return this.authService.forgotPassword(
      body as Parameters<AuthService['forgotPassword']>[0],
      request.ip,
    );
  }

  @Public()
  @Post('reset-password')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Reset password with token' })
  resetPassword(
    @Body(new ZodValidationPipe(resetPasswordRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
  ) {
    return this.authService.resetPassword(
      body as Parameters<AuthService['resetPassword']>[0],
      request.ip,
    );
  }

  @UseGuards(SessionGuard)
  @Post('2fa/setup')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Begin two-factor setup' })
  setupTwoFactor(@CurrentUser() user: { id: string }) {
    return this.authService.setupTwoFactor(user.id);
  }

  @UseGuards(SessionGuard)
  @Post('2fa/confirm')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Confirm two-factor setup' })
  confirmTwoFactor(
    @CurrentUser() user: { id: string },
    @Body(new ZodValidationPipe(twoFactorConfirmRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
  ) {
    return this.authService.confirmTwoFactor(
      user.id,
      body as Parameters<AuthService['confirmTwoFactor']>[1],
      request.ip,
    );
  }

  @Public()
  @Post('2fa/verify')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Complete login with two-factor code' })
  verifyTwoFactor(
    @Body(new ZodValidationPipe(twoFactorVerifyRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
    @Res({ passthrough: true }) reply: FastifyReply,
  ) {
    return this.authService.verifyTwoFactorLogin(
      body as Parameters<AuthService['verifyTwoFactorLogin']>[0],
      reply,
      request.ip,
      request.headers['user-agent'],
    );
  }

  @UseGuards(SessionGuard)
  @Post('2fa/disable')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({ summary: 'Disable two-factor authentication' })
  disableTwoFactor(
    @CurrentUser() user: { id: string },
    @Body(new ZodValidationPipe(twoFactorDisableRequestSchema)) body: unknown,
    @Req() request: FastifyRequest,
  ) {
    return this.authService.disableTwoFactor(
      user.id,
      body as Parameters<AuthService['disableTwoFactor']>[1],
      request.ip,
    );
  }
}
