import {
  Injectable,
  BadRequestException,
  NotFoundException,
  Logger,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import * as bcrypt from 'bcrypt';
import * as crypto from 'crypto';
import { User, PasswordResetToken } from '../../database/entities';
import { EmailService } from '../../common/services/email.service';
import { ConfigService } from '@nestjs/config';
import {
  RequestPasswordResetDto,
  ResetPasswordDto,
  VerifyEmailDto,
} from './dto/password-reset.dto';

@Injectable()
export class PasswordResetService {
  private readonly logger = new Logger(PasswordResetService.name);

  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    @InjectRepository(PasswordResetToken)
    private passwordResetTokenRepository: Repository<PasswordResetToken>,
    private emailService: EmailService,
    private configService: ConfigService,
  ) {}

  /**
   * Request a password reset - sends email with reset link
   */
  async requestPasswordReset(
    dto: RequestPasswordResetDto,
    ipAddress?: string,
    userAgent?: string,
  ): Promise<{ success: boolean; message: string }> {
    const user = await this.userRepository.findOne({
      where: { email: dto.email },
    });

    // Always return success to prevent email enumeration attacks
    if (!user) {
      this.logger.warn(
        `Password reset requested for non-existent email: ${dto.email}`,
      );
      return {
        success: true,
        message:
          'If an account with that email exists, a password reset link has been sent.',
      };
    }

    // Generate secure random token
    const token = crypto.randomBytes(32).toString('hex');
    const expiresAt = new Date();
    expiresAt.setHours(expiresAt.getHours() + 1); // Token expires in 1 hour

    // Create password reset token
    const resetToken = this.passwordResetTokenRepository.create({
      userId: user.id,
      token,
      expiresAt,
      ipAddress,
      userAgent,
    });

    await this.passwordResetTokenRepository.save(resetToken);

    // Generate reset link
    const appUrl = this.configService.get<string>(
      'APP_URL',
      'http://localhost:3030',
    );
    const resetLink = `${appUrl}/reset-password?token=${token}`;

    // Send password reset email
    try {
      await this.emailService.sendPasswordResetEmail(user.email, {
        userName: user.name,
        resetLink,
        expiresIn: '1 hour',
      });

      this.logger.log(`Password reset email sent to ${user.email}`);
    } catch (error) {
      this.logger.error(
        `Failed to send password reset email to ${user.email}:`,
        error,
      );
      // Don't throw error to user - they'll get generic success message
    }

    return {
      success: true,
      message:
        'If an account with that email exists, a password reset link has been sent.',
    };
  }

  /**
   * Reset password using token
   */
  async resetPassword(
    dto: ResetPasswordDto,
    ipAddress?: string,
  ): Promise<{ success: boolean; message: string }> {
    // Find the reset token
    const resetToken = await this.passwordResetTokenRepository.findOne({
      where: { token: dto.token },
      relations: ['user'],
    });

    if (!resetToken) {
      throw new BadRequestException('Invalid or expired password reset token');
    }

    // Check if token is expired
    if (resetToken.isExpired()) {
      throw new BadRequestException(
        'Password reset token has expired. Please request a new one.',
      );
    }

    // Check if token was already used
    if (resetToken.isUsed()) {
      throw new BadRequestException(
        'This password reset token has already been used',
      );
    }

    // Hash new password
    const saltRounds = 10;
    const passwordHash = await bcrypt.hash(dto.newPassword, saltRounds);

    // Update user's password
    resetToken.user.passwordHash = passwordHash;
    await this.userRepository.save(resetToken.user);

    // Mark token as used
    resetToken.usedAt = new Date();
    await this.passwordResetTokenRepository.save(resetToken);

    this.logger.log(`Password reset successful for user ${resetToken.user.id}`);

    return {
      success: true,
      message:
        'Your password has been reset successfully. You can now log in with your new password.',
    };
  }

  /**
   * Send email verification link
   */
  async sendEmailVerification(
    userId: string,
  ): Promise<{ success: boolean; message: string }> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

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

    if (user.emailVerified) {
      return {
        success: true,
        message: 'Your email is already verified',
      };
    }

    // Generate verification token
    const token = crypto.randomBytes(32).toString('hex');

    // Store token in user record
    user.emailVerificationToken = token;
    user.emailVerificationSentAt = new Date();
    await this.userRepository.save(user);

    // Generate verification link
    const appUrl = this.configService.get<string>(
      'APP_URL',
      'http://localhost:3030',
    );
    const verificationLink = `${appUrl}/verify-email?token=${token}`;

    // Send verification email
    try {
      await this.emailService.sendEmailVerificationEmail(user.email, {
        userName: user.name,
        verificationLink,
      });

      this.logger.log(`Email verification sent to ${user.email}`);
    } catch (error) {
      this.logger.error(
        `Failed to send verification email to ${user.email}:`,
        error,
      );
      throw new BadRequestException(
        'Failed to send verification email. Please try again later.',
      );
    }

    return {
      success: true,
      message: 'Verification email sent. Please check your inbox.',
    };
  }

  /**
   * Verify email using token
   */
  async verifyEmail(
    dto: VerifyEmailDto,
  ): Promise<{ success: boolean; message: string }> {
    const user = await this.userRepository.findOne({
      where: { emailVerificationToken: dto.token },
    });

    if (!user) {
      throw new BadRequestException('Invalid or expired verification token');
    }

    if (user.emailVerified) {
      return {
        success: true,
        message: 'Your email is already verified',
      };
    }

    // Check if token is too old (expires after 24 hours)
    const tokenAge =
      new Date().getTime() - user.emailVerificationSentAt.getTime();
    const maxAge = 24 * 60 * 60 * 1000; // 24 hours in milliseconds

    if (tokenAge > maxAge) {
      throw new BadRequestException(
        'Verification token has expired. Please request a new one.',
      );
    }

    // Mark email as verified
    user.emailVerified = true;
    user.emailVerificationToken = null;
    user.emailVerificationSentAt = null;
    await this.userRepository.save(user);

    this.logger.log(`Email verified for user ${user.id}`);

    return {
      success: true,
      message: 'Your email has been verified successfully!',
    };
  }

  /**
   * Resend email verification
   */
  async resendEmailVerification(
    userId: string,
  ): Promise<{ success: boolean; message: string }> {
    const user = await this.userRepository.findOne({
      where: { id: userId },
    });

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

    if (user.emailVerified) {
      return {
        success: true,
        message: 'Your email is already verified',
      };
    }

    // Check if we recently sent a verification email (rate limiting)
    if (user.emailVerificationSentAt) {
      const timeSinceLastSent =
        new Date().getTime() - user.emailVerificationSentAt.getTime();
      const minInterval = 2 * 60 * 1000; // 2 minutes in milliseconds

      if (timeSinceLastSent < minInterval) {
        throw new BadRequestException(
          'Please wait at least 2 minutes before requesting another verification email',
        );
      }
    }

    return await this.sendEmailVerification(userId);
  }
}
