import {
  BadRequestException,
  Injectable,
  InternalServerErrorException,
  Logger,
  NotFoundException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectRepository } from '@nestjs/typeorm';
import { createHash, randomInt, timingSafeEqual } from 'crypto';
import * as nodemailer from 'nodemailer';
import { EmailVerificationCode } from '@/entities/email-verification-code.entity';
import { User } from '@/entities/user.entity';
import { IsNull, Repository } from 'typeorm';

const CODE_TTL_MS = 2 * 60 * 1000;
const MAX_FAILED_ATTEMPTS = 5;

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

  constructor(
    @InjectRepository(User)
    private readonly userRepository: Repository<User>,
    @InjectRepository(EmailVerificationCode)
    private readonly codeRepository: Repository<EmailVerificationCode>,
    private readonly configService: ConfigService,
  ) {}

  async sendCode(email: string) {
    const normalizedEmail = email.trim().toLowerCase();
    const user = await this.userRepository.findOne({
      where: { email: normalizedEmail },
    });
    if (!user) throw new NotFoundException('Không tìm thấy tài khoản');
    if (user.isEmailVerified) {
      throw new BadRequestException('Tài khoản đã được xác thực');
    }

    await this.codeRepository.delete({ userId: user.id, usedAt: IsNull() });
    const code = randomInt(0, 1_000_000).toString().padStart(6, '0');
    const expiresAt = new Date(Date.now() + CODE_TTL_MS);
    const entity = await this.codeRepository.save(
      this.codeRepository.create({
        userId: user.id,
        codeHash: this.hashCode(user.id, code),
        expiresAt,
        usedAt: null,
        failedAttempts: 0,
      }),
    );

    try {
      await this.sendVerificationEmail(user.email, user.name, code);
    } catch (error) {
      await this.codeRepository.delete(entity.id);
      this.logger.error(
        `Could not send verification email to ${user.email}`,
        error instanceof Error ? error.stack : undefined,
      );
      throw new InternalServerErrorException(
        'Không thể gửi email xác thực. Vui lòng thử lại sau',
      );
    }

    return {
      message: 'Mã xác thực đã được gửi',
      email: user.email,
      expiresAt: expiresAt.toISOString(),
      expiresInSeconds: CODE_TTL_MS / 1000,
    };
  }

  async verify(email: string, code: string) {
    const user = await this.userRepository.findOne({
      where: { email: email.trim().toLowerCase() },
    });
    if (!user) throw new BadRequestException('Mã xác thực không hợp lệ');
    if (user.isEmailVerified) {
      return { message: 'Tài khoản đã được xác thực' };
    }

    const verification = await this.codeRepository.findOne({
      where: { userId: user.id, usedAt: IsNull() },
      order: { createdAt: 'DESC' },
    });
    if (!verification) {
      throw new BadRequestException('Mã xác thực không tồn tại hoặc đã được sử dụng');
    }
    if (verification.expiresAt.getTime() <= Date.now()) {
      throw new BadRequestException('Mã xác thực đã hết hạn. Vui lòng gửi lại mã');
    }
    if (verification.failedAttempts >= MAX_FAILED_ATTEMPTS) {
      throw new BadRequestException('Bạn đã nhập sai quá nhiều lần. Vui lòng gửi lại mã');
    }

    const expected = Buffer.from(verification.codeHash, 'hex');
    const actual = Buffer.from(this.hashCode(user.id, code), 'hex');
    if (!timingSafeEqual(expected, actual)) {
      await this.codeRepository.increment(
        { id: verification.id },
        'failedAttempts',
        1,
      );
      throw new BadRequestException('Mã xác thực không chính xác');
    }

    const now = new Date();
    await this.userRepository.manager.transaction(async (manager) => {
      await manager.update(User, user.id, {
        isEmailVerified: true,
        emailVerifiedAt: now,
      });
      await manager.update(EmailVerificationCode, verification.id, {
        usedAt: now,
      });
    });
    return { message: 'Xác thực tài khoản thành công' };
  }

  private hashCode(userId: string, code: string) {
    const secret =
      this.configService.get<string>('EMAIL_VERIFICATION_SECRET') ||
      this.configService.get<string>('JWT_SECRET') ||
      '';
    return createHash('sha256')
      .update(`${userId}:${code}:${secret}`)
      .digest('hex');
  }

  private createTransport() {
    const rawConfig = this.configService.get<string>('NODEMAILER');
    if (rawConfig?.trim().startsWith('{')) {
      return nodemailer.createTransport(JSON.parse(rawConfig));
    }
    const host = this.configService.get<string>('SMTP_HOST');
    const user = this.configService.get<string>('SMTP_USER');
    // Google displays App Passwords in four groups separated by spaces.
    // SMTP authentication expects the same 16 characters without whitespace.
    const pass = this.configService
      .get<string>('SMTP_PASS')
      ?.replace(/\s+/g, '');
    if (!host || !user || !pass) {
      throw new Error('Missing SMTP_HOST, SMTP_USER and SMTP_PASS configuration');
    }
    const port = Number(this.configService.get<string>('SMTP_PORT') || 587);
    return nodemailer.createTransport({
      host,
      port,
      secure:
        this.configService.get<string>('SMTP_SECURE') === 'true' || port === 465,
      auth: { user, pass },
    });
  }

  private async sendVerificationEmail(
    email: string,
    name: string,
    code: string,
  ) {
    const from =
      this.configService.get<string>('MAIL_FROM') ||
      this.configService.get<string>('SMTP_USER');
    await this.createTransport().sendMail({
      from,
      to: email,
      subject: 'Mã xác thực tài khoản Travel Planner',
      text: `Xin chào ${name}, mã xác thực của bạn là ${code}. Mã có hiệu lực trong 2 phút.`,
      html: `
        <div style="font-family:Arial,sans-serif;max-width:520px;margin:auto;padding:24px">
          <h2 style="color:#2563eb">Travel Planner</h2>
          <p>Xin chào ${this.escapeHtml(name)},</p>
          <p>Dùng mã dưới đây để xác thực tài khoản. Mã có hiệu lực trong <b>2 phút</b>.</p>
          <div style="font-size:32px;font-weight:700;letter-spacing:10px;color:#172554;padding:18px 0">${code}</div>
          <p style="color:#64748b">Nếu bạn không yêu cầu mã này, hãy bỏ qua email.</p>
        </div>`,
    });
  }

  private escapeHtml(value: string) {
    const entities: Record<string, string> = {
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      "'": '&#039;',
    };
    return value.replace(/[&<>"']/g, (character) => entities[character]);
  }
}
