import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import * as nodemailer from 'nodemailer';
import PDFDocument from 'pdfkit';
import { join } from 'path';
import { Expense } from '@/entities/expense.entity';
import { GroupMember, GroupRole } from '@/entities/group-member.entity';
import { Trip } from '@/entities/trip.entity';

export type MemberBalance = {
  member: GroupMember;
  paid: number;
  owed: number;
  balance: number;
};

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

  constructor(private readonly configService: ConfigService) {}

  async send(trip: Trip): Promise<{ sent: number; failed: number }> {
    const members = trip.group.members.filter((member) => !!member.user.email);
    const leader =
      members.find((member) => member.role === GroupRole.OWNER) ?? members[0];
    if (!leader) return { sent: 0, failed: 0 };

    const balances = this.calculateBalances(members, trip.expenses ?? []);
    const pdf = await this.createPdf(trip, balances, leader);
    const results = await Promise.allSettled(
      members.map((member) =>
        this.sendToMember(member, trip, balances, leader, pdf),
      ),
    );

    results.forEach((result, index) => {
      if (result.status === 'rejected') {
        this.logger.error(
          `Không thể gửi email kết thúc chuyến đi ${trip.id} đến ${members[index].user.email}`,
          result.reason instanceof Error
            ? result.reason.stack
            : String(result.reason),
        );
      }
    });
    return {
      sent: results.filter((result) => result.status === 'fulfilled').length,
      failed: results.filter((result) => result.status === 'rejected').length,
    };
  }

  calculateBalances(
    members: GroupMember[],
    expenses: Expense[],
  ): MemberBalance[] {
    const balances = new Map(
      members.map((member) => [
        member.user.id,
        { member, paid: 0, owed: 0, balance: 0 },
      ]),
    );
    expenses.forEach((expense) => {
      const payer = balances.get(expense.paidBy?.id);
      if (payer) payer.paid += Number(expense.amount);
      expense.participants?.forEach((participant) => {
        const balance = balances.get(participant.user?.id);
        if (balance) balance.owed += Number(participant.amount);
      });
    });
    return [...balances.values()].map((item) => ({
      ...item,
      balance: this.roundMoney(item.paid - item.owed),
    }));
  }

  private async sendToMember(
    member: GroupMember,
    trip: Trip,
    balances: MemberBalance[],
    leader: GroupMember,
    pdf: Buffer,
  ) {
    const ownBalance = balances.find(
      (item) => item.member.user.id === member.user.id,
    )!;
    const instruction = this.paymentInstruction(ownBalance, leader);
    const from =
      this.configService.get<string>('MAIL_FROM') ||
      this.configService.get<string>('SMTP_USER');
    await this.createTransport().sendMail({
      from,
      to: member.user.email,
      subject: `Kết thúc chuyến đi: ${trip.name}`,
      text: `Xin chào ${member.user.name}, chuyến đi "${trip.name}" đã kết thúc. ${instruction} Chi tiết được đính kèm trong file PDF.`,
      html: `
        <div style="font-family:Arial,sans-serif;max-width:600px;margin:auto;padding:24px;color:#172554">
          <h2 style="color:#2563eb">Chuyến đi đã kết thúc</h2>
          <p>Xin chào ${this.escapeHtml(member.user.name)},</p>
          <p>Chuyến đi <b>${this.escapeHtml(trip.name)}</b> đã được trưởng nhóm kết thúc.</p>
          <div style="padding:16px;background:#eff6ff;border-radius:8px;margin:20px 0"><b>${this.escapeHtml(instruction)}</b></div>
          <p>File PDF đính kèm chứa toàn bộ chi phí đã duyệt và bảng quyết toán của các thành viên.</p>
        </div>`,
      attachments: [{
        filename: `quyet-toan-${this.safeFilename(trip.name)}.pdf`,
        content: pdf,
        contentType: 'application/pdf',
      }],
    });
  }

  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');
    const pass = this.configService.get<string>('SMTP_PASS')?.replace(/\s+/g, '');
    if (!host || !user || !pass) throw new Error('Thiếu cấu hình SMTP');
    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 createPdf(
    trip: Trip,
    balances: MemberBalance[],
    leader: GroupMember,
  ): Promise<Buffer> {
    return new Promise((resolve, reject) => {
      const document = new PDFDocument({ margin: 45, size: 'A4' });
      const chunks: Buffer[] = [];
      document.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
      document.on('end', () => resolve(Buffer.concat(chunks)));
      document.on('error', reject);
      const fontDirectory = join(
        process.cwd(),
        'node_modules',
        'dejavu-fonts-ttf',
        'ttf',
      );
      document.registerFont('ReportRegular', join(fontDirectory, 'DejaVuSans.ttf'));
      document.registerFont(
        'ReportBold',
        join(fontDirectory, 'DejaVuSans-Bold.ttf'),
      );
      document.font('ReportRegular');
      document
        .font('ReportBold')
        .fontSize(20)
        .text('BÁO CÁO QUYẾT TOÁN CHUYẾN ĐI', { align: 'center' });
      document.moveDown();
      document.font('ReportRegular').fontSize(12).text(`Chuyến đi: ${trip.name}`);
      document.text(`Địa điểm: ${trip.location}`);
      document.text(`Thời gian: ${this.formatDate(trip.startDate)} - ${this.formatDate(trip.endDate)}`);
      document.text(`Trưởng nhóm: ${leader.user.name}`);
      document.moveDown();
      document.font('ReportBold').fontSize(15).text('Chi phí đã duyệt');
      document.font('ReportRegular');
      document.moveDown(0.4);
      if (!trip.expenses?.length) document.fontSize(11).text('Không có chi phí đã duyệt.');
      trip.expenses?.forEach((expense, index) => {
        this.ensurePage(document);
        document.fontSize(10).text(
          `${index + 1}. ${expense.title} | ${this.formatMoney(Number(expense.amount))} | Người trả: ${expense.paidBy?.name ?? 'Không rõ'}`,
        );
      });
      document.moveDown();
      document.fontSize(11).text(`Tổng chi phí: ${this.formatMoney(
        (trip.expenses ?? []).reduce((total, expense) => total + Number(expense.amount), 0),
      )}`);
      document.moveDown();
      document.font('ReportBold').fontSize(15).text('Quyết toán theo thành viên');
      document.font('ReportRegular');
      document.moveDown(0.4);
      balances.forEach((balance) => {
        this.ensurePage(document);
        document.fontSize(10).text(
          `${balance.member.user.name}: đã trả ${this.formatMoney(balance.paid)}, phần chi phí ${this.formatMoney(balance.owed)}. ${this.paymentInstruction(balance, leader)}`,
        );
      });
      document.end();
    });
  }

  private paymentInstruction(balance: MemberBalance, leader: GroupMember) {
    const amount = Math.abs(balance.balance);
    if (amount < 0.01) return 'Đã cân bằng, không cần chuyển tiền.';
    if (balance.member.user.id === leader.user.id) {
      return balance.balance > 0
        ? `Cần nhận lại tổng cộng ${this.formatMoney(amount)} từ các thành viên.`
        : `Cần hoàn lại tổng cộng ${this.formatMoney(amount)} cho các thành viên.`;
    }
    return balance.balance > 0
      ? `Được nhận lại ${this.formatMoney(amount)} từ trưởng nhóm ${leader.user.name}.`
      : `Cần chuyển ${this.formatMoney(amount)} cho trưởng nhóm ${leader.user.name}.`;
  }

  private ensurePage(document: InstanceType<typeof PDFDocument>) {
    if (document.y > document.page.height - 70) document.addPage();
  }
  private formatMoney(amount: number) {
    return `${new Intl.NumberFormat('vi-VN', { maximumFractionDigits: 2 })
      .format(this.roundMoney(amount))} đ`;
  }
  private formatDate(value: Date) {
    return new Intl.DateTimeFormat('vi-VN').format(new Date(value));
  }
  private roundMoney(value: number) {
    return Math.round((value + Number.EPSILON) * 100) / 100;
  }
  private safeFilename(value: string) {
    return value.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
      .replace(/đ/g, 'd').replace(/Đ/g, 'D')
      .replace(/[^a-zA-Z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').toLowerCase();
  }
  private escapeHtml(value: string) {
    const entities: Record<string, string> = {
      '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;',
    };
    return value.replace(/[&<>"']/g, (character) => entities[character]);
  }
}
