import {
  Controller,
  Post,
  Body,
  HttpCode,
  HttpStatus,
  UseGuards,
  Get,
  Inject,
  NotFoundException,
  InternalServerErrorException,
  Logger,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { AuthService } from '../auth/auth.service';
import {
  LoginDto,
  RegisterDto,
  AuthResponseDto,
  RefreshTokenDto,
  ForgotPasswordDto,
  ResetPasswordDto,
  ContactFormDto,
} from '../dto/auth-login.dto';
import { EmailPort, EMAIL_PORT } from '@domain/ports/out/email.port';
import { Public } from '../decorators/public.decorator';
import { CurrentUser, UserPayload } from '../decorators/current-user.decorator';
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
import { UserRepository, USER_REPOSITORY } from '@domain/ports/out/user.repository';
import { UserMapper } from '../mappers/user.mapper';
import { InvitationService } from '../services/invitation.service';

/**
 * Authentication Controller
 *
 * Handles user authentication endpoints:
 * - POST /auth/register - User registration
 * - POST /auth/login - User login
 * - POST /auth/refresh - Token refresh
 * - POST /auth/logout - User logout (placeholder)
 * - GET /auth/me - Get current user profile
 */
@ApiTags('Authentication')
@Controller('auth')
export class AuthController {
  private readonly logger = new Logger(AuthController.name);

  constructor(
    private readonly authService: AuthService,
    @Inject(USER_REPOSITORY) private readonly userRepository: UserRepository,
    private readonly invitationService: InvitationService,
    @Inject(EMAIL_PORT) private readonly emailService: EmailPort
  ) {}

  /**
   * Register a new user
   *
   * Creates a new user account and returns access + refresh tokens.
   *
   * @param dto - Registration data (email, password, firstName, lastName, organizationId)
   * @returns Access token, refresh token, and user info
   */
  @Public()
  @Post('register')
  @HttpCode(HttpStatus.CREATED)
  @ApiOperation({
    summary: 'Register new user',
    description: 'Create a new user account with email and password. Returns JWT tokens.',
  })
  @ApiResponse({
    status: 201,
    description: 'User successfully registered',
    type: AuthResponseDto,
  })
  @ApiResponse({
    status: 409,
    description: 'User with this email already exists',
  })
  @ApiResponse({
    status: 400,
    description: 'Validation error (invalid email, weak password, etc.)',
  })
  async register(@Body() dto: RegisterDto): Promise<AuthResponseDto> {
    // If invitation token is provided, verify and use it
    let invitationOrganizationId: string | undefined;
    let invitationRole: string | undefined;

    if (dto.invitationToken) {
      const invitation = await this.invitationService.verifyInvitation(dto.invitationToken);

      // Verify email matches invitation
      if (invitation.email.toLowerCase() !== dto.email.toLowerCase()) {
        throw new NotFoundException('Invitation email does not match registration email');
      }

      invitationOrganizationId = invitation.organizationId;
      invitationRole = invitation.role;

      // Override firstName/lastName from invitation if not provided
      dto.firstName = dto.firstName || invitation.firstName;
      dto.lastName = dto.lastName || invitation.lastName;
    }

    const result = await this.authService.register(
      dto.email,
      dto.password,
      dto.firstName,
      dto.lastName,
      invitationOrganizationId || dto.organizationId,
      dto.organization,
      invitationRole
    );

    // Mark invitation as used if provided
    if (dto.invitationToken) {
      await this.invitationService.markInvitationAsUsed(dto.invitationToken);
    }

    return {
      accessToken: result.accessToken,
      refreshToken: result.refreshToken,
      user: result.user,
    };
  }

  /**
   * Login with email and password
   *
   * Authenticates a user and returns access + refresh tokens.
   *
   * @param dto - Login credentials (email, password)
   * @returns Access token, refresh token, and user info
   */
  @Public()
  @Post('login')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'User login',
    description: 'Authenticate with email and password. Returns JWT tokens.',
  })
  @ApiResponse({
    status: 200,
    description: 'Login successful',
    type: AuthResponseDto,
  })
  @ApiResponse({
    status: 401,
    description: 'Invalid credentials or inactive account',
  })
  async login(@Body() dto: LoginDto): Promise<AuthResponseDto> {
    const result = await this.authService.login(dto.email, dto.password);

    return {
      accessToken: result.accessToken,
      refreshToken: result.refreshToken,
      user: result.user,
    };
  }

  /**
   * Refresh access token
   *
   * Obtains a new access token using a valid refresh token.
   *
   * @param dto - Refresh token
   * @returns New access token
   */
  @Public()
  @Post('refresh')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Refresh access token',
    description:
      'Get a new access token using a valid refresh token. Refresh tokens are long-lived (7 days).',
  })
  @ApiResponse({
    status: 200,
    description: 'Token refreshed successfully',
    schema: {
      properties: {
        accessToken: { type: 'string', example: 'eyJhbGciOiJIUzI1NiIs...' },
      },
    },
  })
  @ApiResponse({
    status: 401,
    description: 'Invalid or expired refresh token',
  })
  async refresh(@Body() dto: RefreshTokenDto): Promise<{ accessToken: string }> {
    const result = await this.authService.refreshAccessToken(dto.refreshToken);

    return { accessToken: result.accessToken };
  }

  /**
   * Logout (placeholder)
   *
   * Currently a no-op endpoint. With JWT, logout is typically handled client-side
   * by removing tokens. For more security, implement token blacklisting with Redis.
   *
   * @returns Success message
   */
  @UseGuards(JwtAuthGuard)
  @Post('logout')
  @HttpCode(HttpStatus.OK)
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Logout',
    description: 'Logout the current user. Currently handled client-side by removing tokens.',
  })
  @ApiResponse({
    status: 200,
    description: 'Logout successful',
    schema: {
      properties: {
        message: { type: 'string', example: 'Logout successful' },
      },
    },
  })
  async logout(): Promise<{ message: string }> {
    // TODO: Implement token blacklisting with Redis for more security
    // For now, logout is handled client-side by removing tokens
    return { message: 'Logout successful' };
  }

  /**
   * Contact form — forwards message to contact@xpeditis.com
   */
  @Public()
  @Post('contact')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Contact form',
    description: 'Send a contact message to the Xpeditis team.',
  })
  @ApiResponse({ status: 200, description: 'Message sent successfully' })
  async contact(@Body() dto: ContactFormDto): Promise<{ message: string }> {
    const subjectLabels: Record<string, string> = {
      demo: 'Demande de démonstration',
      pricing: 'Questions sur les tarifs',
      partnership: 'Partenariat',
      support: 'Support technique',
      press: 'Relations presse',
      careers: 'Recrutement',
      other: 'Autre',
    };

    const subjectLabel = subjectLabels[dto.subject] || dto.subject;

    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <div style="background: #10183A; padding: 24px; border-radius: 8px 8px 0 0;">
          <h1 style="color: #34CCCD; margin: 0; font-size: 20px;">Nouveau message de contact</h1>
        </div>
        <div style="background: #f9f9f9; padding: 24px; border: 1px solid #e0e0e0;">
          <table style="width: 100%; border-collapse: collapse;">
            <tr>
              <td style="padding: 8px 0; color: #666; width: 130px; font-size: 14px;">Nom</td>
              <td style="padding: 8px 0; color: #222; font-weight: bold; font-size: 14px;">${dto.firstName} ${dto.lastName}</td>
            </tr>
            <tr>
              <td style="padding: 8px 0; color: #666; font-size: 14px;">Email</td>
              <td style="padding: 8px 0; font-size: 14px;"><a href="mailto:${dto.email}" style="color: #34CCCD;">${dto.email}</a></td>
            </tr>
            ${dto.company ? `<tr><td style="padding: 8px 0; color: #666; font-size: 14px;">Entreprise</td><td style="padding: 8px 0; color: #222; font-size: 14px;">${dto.company}</td></tr>` : ''}
            ${dto.phone ? `<tr><td style="padding: 8px 0; color: #666; font-size: 14px;">Téléphone</td><td style="padding: 8px 0; color: #222; font-size: 14px;">${dto.phone}</td></tr>` : ''}
            <tr>
              <td style="padding: 8px 0; color: #666; font-size: 14px;">Sujet</td>
              <td style="padding: 8px 0; color: #222; font-size: 14px;">${subjectLabel}</td>
            </tr>
          </table>
          <div style="margin-top: 16px; padding-top: 16px; border-top: 1px solid #ddd;">
            <p style="color: #666; font-size: 14px; margin: 0 0 8px 0;">Message :</p>
            <p style="color: #222; font-size: 14px; white-space: pre-wrap; margin: 0;">${dto.message}</p>
          </div>
        </div>
        <div style="background: #f0f0f0; padding: 12px 24px; border-radius: 0 0 8px 8px; text-align: center;">
          <p style="color: #999; font-size: 12px; margin: 0;">Xpeditis — Formulaire de contact</p>
        </div>
      </div>
    `;

    try {
      await this.emailService.send({
        to: 'contact@xpeditis.com',
        replyTo: dto.email,
        subject: `[Contact] ${subjectLabel} — ${dto.firstName} ${dto.lastName}`,
        html,
      });
    } catch (error) {
      this.logger.error(`Failed to send contact email: ${error}`);
      throw new InternalServerErrorException(
        "Erreur lors de l'envoi du message. Veuillez réessayer."
      );
    }

    return { message: 'Message envoyé avec succès.' };
  }

  /**
   * Forgot password — sends reset email
   */
  @Public()
  @Post('forgot-password')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Forgot password',
    description: 'Send a password reset email. Always returns 200 to avoid user enumeration.',
  })
  @ApiResponse({ status: 200, description: 'Reset email sent (if account exists)' })
  async forgotPassword(@Body() dto: ForgotPasswordDto): Promise<{ message: string }> {
    await this.authService.forgotPassword(dto.email);
    return {
      message: 'Si un compte existe avec cet email, vous recevrez un lien de réinitialisation.',
    };
  }

  /**
   * Reset password using token from email
   */
  @Public()
  @Post('reset-password')
  @HttpCode(HttpStatus.OK)
  @ApiOperation({
    summary: 'Reset password',
    description: 'Reset user password using the token received by email.',
  })
  @ApiResponse({ status: 200, description: 'Password reset successfully' })
  @ApiResponse({ status: 400, description: 'Invalid or expired token' })
  async resetPassword(@Body() dto: ResetPasswordDto): Promise<{ message: string }> {
    await this.authService.resetPassword(dto.token, dto.newPassword);
    return { message: 'Mot de passe réinitialisé avec succès.' };
  }

  /**
   * Get current user profile
   *
   * Returns the profile of the currently authenticated user with complete details.
   *
   * @param user - Current user from JWT token
   * @returns User profile with firstName, lastName, etc.
   */
  @UseGuards(JwtAuthGuard)
  @Get('me')
  @ApiBearerAuth()
  @ApiOperation({
    summary: 'Get current user profile',
    description: 'Returns the complete profile of the authenticated user.',
  })
  @ApiResponse({
    status: 200,
    description: 'User profile retrieved successfully',
    schema: {
      properties: {
        id: { type: 'string', format: 'uuid' },
        email: { type: 'string', format: 'email' },
        firstName: { type: 'string' },
        lastName: { type: 'string' },
        role: { type: 'string', enum: ['ADMIN', 'MANAGER', 'USER', 'VIEWER'] },
        organizationId: { type: 'string', format: 'uuid' },
        isActive: { type: 'boolean' },
        createdAt: { type: 'string', format: 'date-time' },
        updatedAt: { type: 'string', format: 'date-time' },
      },
    },
  })
  @ApiResponse({
    status: 401,
    description: 'Unauthorized - invalid or missing token',
  })
  async getProfile(@CurrentUser() user: UserPayload) {
    // Fetch complete user details from database
    const fullUser = await this.userRepository.findById(user.id);

    if (!fullUser) {
      throw new NotFoundException('User not found');
    }

    // Return complete user data with firstName and lastName
    return UserMapper.toDto(fullUser);
  }
}
