import {
  Body,
  Controller,
  Get,
  Patch,
  Post,
  Req,
  Res,
  UseGuards,
} from '@nestjs/common';
import { CommandBus, QueryBus } from '@nestjs/cqrs';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiBody } from '@nestjs/swagger';
import { Throttle } from '@nestjs/throttler';
import { Request, Response } from 'express';
import { EndpointRateLimit, EndpointRateLimitGuard, UnauthorizedException } from '@modules/shared';
import { LoginUserCommand } from '../../application/commands/login-user/login-user.command';
import { LoginResult } from '../../application/commands/login-user/login-user.handler';
import { RefreshTokenCommand } from '../../application/commands/refresh-token/refresh-token.command';
import { RegisterUserCommand } from '../../application/commands/register-user/register-user.command';
import { VerifyKycCommand } from '../../application/commands/verify-kyc/verify-kyc.command';
import { AgentDto } from '../../application/queries/get-agent-by-user-id/get-agent-by-user-id.handler';
import { GetAgentByUserIdQuery } from '../../application/queries/get-agent-by-user-id/get-agent-by-user-id.query';
import { UserProfileDto } from '../../application/queries/get-profile/get-profile.handler';
import { GetProfileQuery } from '../../application/queries/get-profile/get-profile.query';
import { TokenService, JwtPayload, TokenPair } from '../../infrastructure/services/token.service';
import { LocalStrategyResult } from '../../infrastructure/strategies/local.strategy';
import { CurrentUser } from '../decorators/current-user.decorator';
import { Roles } from '../decorators/roles.decorator';
import { LoginDto } from '../dto/login.dto';
import { RefreshTokenDto } from '../dto/refresh-token.dto';
import { RegisterDto } from '../dto/register.dto';
import { VerifyKycDto } from '../dto/verify-kyc.dto';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { LocalAuthGuard } from '../guards/local-auth.guard';
import { RolesGuard } from '../guards/roles.guard';

const IS_PRODUCTION = process.env['NODE_ENV'] === 'production';
const IS_TEST = process.env['NODE_ENV'] === 'test';
const AUTH_RATE_LIMIT = IS_TEST ? 10_000 : 5;
const ACCESS_TOKEN_MAX_AGE = 15 * 60 * 1000; // 15 minutes
const REFRESH_TOKEN_MAX_AGE = 30 * 24 * 60 * 60 * 1000; // 30 days
const AUTH_COOKIE_MAX_AGE = 30 * 24 * 60 * 60 * 1000; // 30 days

const SAME_SITE = IS_PRODUCTION ? 'strict' : 'lax';

function setAuthCookies(res: Response, tokens: TokenPair): void {
  res.cookie('access_token', tokens.accessToken, {
    httpOnly: true,
    secure: IS_PRODUCTION,
    sameSite: SAME_SITE,
    path: '/',
    maxAge: ACCESS_TOKEN_MAX_AGE,
  });
  res.cookie('refresh_token', tokens.refreshToken, {
    httpOnly: true,
    secure: IS_PRODUCTION,
    sameSite: SAME_SITE,
    path: '/',
    maxAge: REFRESH_TOKEN_MAX_AGE,
  });
  res.cookie('goodgo_authenticated', '1', {
    httpOnly: false,
    secure: IS_PRODUCTION,
    sameSite: 'lax',
    path: '/',
    maxAge: AUTH_COOKIE_MAX_AGE,
  });
}

function clearAuthCookies(res: Response): void {
  res.clearCookie('access_token', { path: '/' });
  res.clearCookie('refresh_token', { path: '/' });
  res.clearCookie('goodgo_authenticated', { path: '/' });
}

@ApiTags('auth')
@Controller('auth')
export class AuthController {
  constructor(
    private readonly commandBus: CommandBus,
    private readonly queryBus: QueryBus,
    private readonly tokenService: TokenService,
  ) {}

  @Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
  @EndpointRateLimit({ limit: IS_TEST ? 10_000 : 3, windowSeconds: 60, keyStrategy: 'ip' })
  @UseGuards(EndpointRateLimitGuard)
  @Post('register')
  @ApiOperation({ summary: 'Register a new user' })
  @ApiResponse({ status: 201, description: 'User registered, auth cookies set' })
  @ApiResponse({ status: 400, description: 'Validation error' })
  @ApiResponse({ status: 409, description: 'Phone already registered' })
  async register(
    @Body() dto: RegisterDto,
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ message: string; accessToken: string; refreshToken: string }> {
    const tokens: TokenPair = await this.commandBus.execute(
      new RegisterUserCommand(dto.phone, dto.password, dto.fullName, dto.email),
    );
    setAuthCookies(res, tokens);
    return {
      message: 'Đăng ký thành công',
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  @Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
  @EndpointRateLimit({ limit: IS_TEST ? 10_000 : 5, windowSeconds: 60, keyStrategy: 'ip' })
  @UseGuards(EndpointRateLimitGuard, LocalAuthGuard)
  @Post('login')
  @ApiOperation({ summary: 'Login with phone and password' })
  @ApiBody({ type: LoginDto })
  @ApiResponse({ status: 201, description: 'Login successful, auth cookies set (or MFA challenge returned)' })
  @ApiResponse({ status: 401, description: 'Invalid credentials' })
  async login(
    @CurrentUser() user: LocalStrategyResult,
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ message: string; accessToken?: string; refreshToken?: string; requiresMfa?: boolean; challengeId?: string }> {
    const result: LoginResult = await this.commandBus.execute(
      new LoginUserCommand(user.id, user.phone, user.role, user.isMfaRequired),
    );

    if (result.requiresMfa) {
      return {
        message: 'Yêu cầu xác thực MFA',
        requiresMfa: true,
        challengeId: result.challengeId,
      };
    }

    setAuthCookies(res, result.tokens!);
    return {
      message: 'Đăng nhập thành công',
      accessToken: result.tokens!.accessToken,
      refreshToken: result.tokens!.refreshToken,
    };
  }

  @Throttle({ default: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT }, auth: { ttl: 3_600_000, limit: AUTH_RATE_LIMIT } })
  @Post('refresh')
  @ApiOperation({ summary: 'Refresh access token using refresh cookie' })
  @ApiResponse({ status: 201, description: 'New auth cookies set' })
  @ApiResponse({ status: 401, description: 'Invalid or expired refresh token' })
  async refresh(
    @Req() req: Request,
    @Res({ passthrough: true }) res: Response,
    @Body() dto?: RefreshTokenDto,
  ): Promise<{ message: string; accessToken: string; refreshToken: string }> {
    const refreshToken =
      (req.cookies?.['refresh_token'] as string | undefined) ?? dto?.refreshToken;
    if (!refreshToken) {
      throw new UnauthorizedException('Refresh token not found');
    }
    const tokens: TokenPair = await this.commandBus.execute(
      new RefreshTokenCommand(refreshToken),
    );
    setAuthCookies(res, tokens);
    return {
      message: 'Token refreshed',
      accessToken: tokens.accessToken,
      refreshToken: tokens.refreshToken,
    };
  }

  @Post('logout')
  @ApiOperation({ summary: 'Logout and clear auth cookies' })
  @ApiResponse({ status: 200, description: 'Logged out' })
  async logout(@Res({ passthrough: true }) res: Response): Promise<{ message: string }> {
    clearAuthCookies(res);
    return { message: 'Đã đăng xuất' };
  }

  @Post('exchange-token')
  @ApiOperation({ summary: 'Exchange OAuth token pair for httpOnly cookies' })
  @ApiResponse({ status: 201, description: 'Auth cookies set' })
  @ApiResponse({ status: 401, description: 'Invalid access token' })
  async exchangeToken(
    @Body() body: { accessToken: string; refreshToken: string; expiresIn?: number },
    @Res({ passthrough: true }) res: Response,
  ): Promise<{ message: string }> {
    const payload = this.tokenService.verifyAccessToken(body.accessToken);
    if (!payload) {
      throw new UnauthorizedException('Invalid access token');
    }
    setAuthCookies(res, {
      accessToken: body.accessToken,
      refreshToken: body.refreshToken,
      expiresIn: body.expiresIn ?? 900,
    });
    return { message: 'Auth cookies set' };
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile')
  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Get current user profile' })
  @ApiResponse({ status: 200, description: 'User profile returned' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getProfile(@CurrentUser() user: JwtPayload): Promise<UserProfileDto> {
    return this.queryBus.execute(new GetProfileQuery(user.sub));
  }

  @UseGuards(JwtAuthGuard)
  @Get('profile/agent')
  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Get agent profile for current user' })
  @ApiResponse({ status: 200, description: 'Agent profile returned (null if not an agent)' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  async getAgentProfile(@CurrentUser() user: JwtPayload): Promise<AgentDto | null> {
    return this.queryBus.execute(new GetAgentByUserIdQuery(user.sub));
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('ADMIN')
  @Patch('kyc')
  @ApiBearerAuth('JWT')
  @ApiOperation({ summary: 'Verify user KYC (admin only)' })
  @ApiResponse({ status: 200, description: 'KYC status updated' })
  @ApiResponse({ status: 401, description: 'Unauthorized' })
  @ApiResponse({ status: 403, description: 'Forbidden — admin only' })
  async verifyKyc(
    @Body() dto: VerifyKycDto & { userId: string },
  ): Promise<{ message: string }> {
    await this.commandBus.execute(
      new VerifyKycCommand(dto.userId, dto.kycStatus, dto.kycData),
    );
    return { message: 'KYC status đã được cập nhật' };
  }
}
