import { Injectable, Logger } from '@nestjs/common';
import { readFileSync } from 'fs';
import { join } from 'path';
import * as Handlebars from 'handlebars';
import type SMTPTransport from 'nodemailer/lib/smtp-transport';
import { AppConfigService } from '../config/config.service';

/** Ticket #28 — quel template d'email envoyer après validation de compte */
export type ValidationAccountEmailKind = 'parent' | 'am';

type MailTransporter = {
  sendMail: (options: Record<string, unknown>) => Promise<unknown>;
};

/** Erreurs SMTP temporaires (surcharge auth, coupure réseau…) — retentables. */
export function isTransientSmtpError(error: unknown): boolean {
  const message = error instanceof Error ? error.message : String(error);
  const code = typeof error === 'object' && error !== null && 'code' in error
    ? String((error as { code?: unknown }).code ?? '')
    : '';
  const responseCode =
    typeof error === 'object' && error !== null && 'responseCode' in error
      ? Number((error as { responseCode?: unknown }).responseCode)
      : undefined;

  if (responseCode === 454 || responseCode === 421 || responseCode === 450 || responseCode === 451) {
    return true;
  }

  const transientCodes = new Set(['ECONNRESET', 'ETIMEDOUT', 'ESOCKET', 'ECONNREFUSED', 'EPIPE']);
  if (transientCodes.has(code)) {
    return true;
  }

  const transientPatterns = [
    /temporary authentication failure/i,
    /connection lost to authentication server/i,
    /connection reset/i,
    /timeout/i,
    /try again later/i,
  ];
  return transientPatterns.some((pattern) => pattern.test(message));
}

@Injectable()
export class MailService {
  private readonly logger = new Logger(MailService.name);
  private readonly smtpMaxAttempts = 3;
  private readonly smtpRetryBaseDelayMs = 1000;

  /** Cache des templates Handlebars compilés (fichiers .hbs) */
  private readonly compiledTemplates = new Map<ValidationAccountEmailKind, Handlebars.TemplateDelegate>();

  /** Transporteur SMTP réutilisé (évite une auth TCP/SASL par email). */
  private transporter: MailTransporter | null = null;
  private transporterConfigKey: string | null = null;

  constructor(private readonly configService: AppConfigService) {}

  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  private buildSmtpTransportConfig(): SMTPTransport.Options {
    const smtpHost = this.configService.get<string>('smtp_host');
    const smtpPort = this.configService.get<number>('smtp_port');
    const smtpSecure = this.configService.get<boolean>('smtp_secure');
    const smtpAuthRequired = this.configService.get<boolean>('smtp_auth_required');
    const smtpUser = this.configService.get<string>('smtp_user');
    const smtpPassword = this.configService.get<string>('smtp_password');

    const transportConfig = {
      host: smtpHost,
      port: smtpPort,
      secure: smtpSecure,
      pool: true,
      maxConnections: 1,
      maxMessages: 100,
      ...(smtpAuthRequired && smtpUser && smtpPassword
        ? { auth: { user: smtpUser, pass: smtpPassword } }
        : {}),
    } as SMTPTransport.Options;

    return transportConfig;
  }

  private getSmtpConfigKey(config: SMTPTransport.Options): string {
    const auth = config.auth as { user?: string; pass?: string } | undefined;
    return JSON.stringify({
      host: config.host,
      port: config.port,
      secure: config.secure,
      user: auth?.user ?? '',
      pass: auth?.pass ?? '',
    });
  }

  private resetTransporter(): void {
    const current = this.transporter as { close?: () => void } | null;
    current?.close?.();
    this.transporter = null;
    this.transporterConfigKey = null;
  }

  private async getTransporter(): Promise<MailTransporter> {
    const transportConfig = this.buildSmtpTransportConfig();
    const configKey = this.getSmtpConfigKey(transportConfig);

    if (this.transporter && this.transporterConfigKey === configKey) {
      return this.transporter;
    }

    this.resetTransporter();
    const nodemailer = await import('nodemailer');
    this.transporter = nodemailer.createTransport(transportConfig) as MailTransporter;
    this.transporterConfigKey = configKey;
    return this.transporter;
  }

  private templateBasename(kind: ValidationAccountEmailKind): string {
    const map: Record<ValidationAccountEmailKind, string> = {
      parent: 'account-validated-parent',
      am: 'account-validated-am',
    };
    return map[kind];
  }

  private getCompiledTemplate(kind: ValidationAccountEmailKind): Handlebars.TemplateDelegate {
    let compiled = this.compiledTemplates.get(kind);
    if (!compiled) {
      const filePath = join(__dirname, 'templates', `${this.templateBasename(kind)}.hbs`);
      const source = readFileSync(filePath, 'utf8');
      compiled = Handlebars.compile(source);
      this.compiledTemplates.set(kind, compiled);
    }
    return compiled;
  }

  /**
   * Email post-validation : lien création MDP (token existant ou régénéré côté appelant).
   * Ticket #28 — app_name, app_url, expéditeur via ConfigService (sendEmail).
   */
  async sendValidatedAccountPasswordSetupEmail(
    recipient: {
      email: string;
      prenom: string;
      nom: string;
      token: string;
      numeroDossier?: string | null;
    },
    kind: ValidationAccountEmailKind,
  ): Promise<void> {
    const appName = this.configService.get<string>('app_name', "P'titsPas");
    const appUrl = (this.configService.get<string>('app_url', 'https://app.ptits-pas.fr') || '').replace(/\/+$/, '');

    const createPasswordUrl = `${appUrl}/create-password?token=${encodeURIComponent(recipient.token)}`;

    const data: Record<string, string> = {
      prenom: recipient.prenom || '',
      nom: recipient.nom || '',
      appName,
      appUrl,
      createPasswordUrl,
      numeroDossier: recipient.numeroDossier || '',
    };

    const html = this.getCompiledTemplate(kind)(data) as string;

    const subjects: Record<ValidationAccountEmailKind, string> = {
      parent: `${appName} — Compte parent validé : créez votre mot de passe`,
      am: `${appName} — Inscription validée : créez votre mot de passe`,
    };

    await this.sendEmail(recipient.email, subjects[kind], html);
  }

  /**
   * Envoi d'un email générique
   * @param to Destinataire
   * @param subject Sujet
   * @param html Contenu HTML
   * @param text Contenu texte (optionnel)
   */
  async sendEmail(to: string, subject: string, html: string, text?: string): Promise<void> {
    const emailFromName = this.configService.get<string>('email_from_name');
    const emailFromAddress = this.configService.get<string>('email_from_address');
    const mailOptions = {
      from: `"${emailFromName}" <${emailFromAddress}>`,
      to,
      subject,
      text: text || html.replace(/<[^>]*>?/gm, ''),
      html,
    };

    let lastError: unknown;

    for (let attempt = 1; attempt <= this.smtpMaxAttempts; attempt++) {
      try {
        const transporter = await this.getTransporter();
        await transporter.sendMail(mailOptions);
        this.logger.log(`📧 Email envoyé à ${to} : ${subject}`);
        return;
      } catch (error) {
        lastError = error;
        const canRetry = attempt < this.smtpMaxAttempts && isTransientSmtpError(error);

        if (canRetry) {
          const delayMs = this.smtpRetryBaseDelayMs * 2 ** (attempt - 1);
          this.logger.warn(
            `SMTP temporairement indisponible pour ${to} (tentative ${attempt}/${this.smtpMaxAttempts}), nouvel essai dans ${delayMs}ms`,
            error instanceof Error ? error.message : error,
          );
          this.resetTransporter();
          await this.sleep(delayMs);
          continue;
        }

        this.logger.error(`❌ Erreur lors de l'envoi de l'email à ${to}`, error);
        this.resetTransporter();
        throw error;
      }
    }

    this.logger.error(`❌ Erreur lors de l'envoi de l'email à ${to}`, lastError);
    this.resetTransporter();
    throw lastError;
  }

  /**
   * Envoi de l'email de bienvenue pour un gestionnaire
   * @param to Email du gestionnaire
   * @param prenom Prénom
   * @param nom Nom
   * @param token Token de création de mot de passe (si applicable) ou mot de passe temporaire (si applicable)
   * @note Pour l'instant, on suppose que le gestionnaire doit définir son mot de passe via "Mot de passe oublié" ou un lien d'activation
   * Mais le ticket #17 parle de "Flag changement_mdp_obligatoire = TRUE", ce qui implique qu'on lui donne un mot de passe temporaire ou qu'on lui envoie un lien.
   * Le ticket #24 parle de "API Création mot de passe" via token.
   * Pour le ticket #17, on crée le gestionnaire avec un mot de passe (hashé).
   * Si on suit le ticket #35 (Frontend), on saisit un mot de passe.
   * Donc on envoie juste un email de confirmation de création de compte.
   */
  async sendGestionnaireWelcomeEmail(to: string, prenom: string, nom: string): Promise<void> {
    const appName = this.configService.get<string>('app_name', 'P\'titsPas');
    const appUrl = this.configService.get<string>('app_url', 'https://app.ptits-pas.fr');

    const subject = `Bienvenue sur ${appName}`;
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2 style="color: #4CAF50;">Bienvenue ${prenom} ${nom} !</h2>
        <p>Votre compte gestionnaire sur <strong>${appName}</strong> a été créé avec succès.</p>
        <p>Vous pouvez dès à présent vous connecter avec l'adresse email <strong>${to}</strong> et le mot de passe qui vous a été communiqué.</p>
        <p>Lors de votre première connexion, il vous sera demandé de modifier votre mot de passe pour des raisons de sécurité.</p>
        <div style="text-align: center; margin: 30px 0;">
          <a href="${appUrl}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Accéder à l'application</a>
        </div>
        <hr style="border: 1px solid #eee; margin: 20px 0;">
        <p style="color: #666; font-size: 12px;">
          Cet email a été envoyé automatiquement. Merci de ne pas y répondre.
        </p>
      </div>
    `;

    await this.sendEmail(to, subject, html);
  }

  /**
   * Accusé de réception inscription (parent ou assistante maternelle) :
   * demande enregistrée + n° de dossier. En attente de validation ; pas de lien création MDP à ce stade.
   */
  async sendRegistrationPendingEmail(
    to: string,
    prenom: string,
    nom: string,
    numeroDossier: string,
  ): Promise<void> {
    const appName = this.configService.get<string>('app_name', "P'titsPas");
    const appUrl = this.configService.get<string>('app_url', 'https://app.ptits-pas.fr');

    const safe = (s: string) =>
      (s || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');

    const subject = `Votre demande d'inscription sur ${appName} — dossier ${safe(numeroDossier)}`;
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2 style="color: #4CAF50;">Bonjour ${safe(prenom)} ${safe(nom)},</h2>
        <p>Nous avons bien enregistré votre demande de création de compte sur <strong>${safe(appName)}</strong>.</p>
        <p><strong>Numéro de dossier :</strong> ${safe(numeroDossier)}</p>
        <p>Votre dossier est <strong>en attente de validation</strong> par notre équipe. Vous recevrez un email lorsqu’il aura été traité.</p>
        <div style="text-align: center; margin: 30px 0;">
          <a href="${appUrl}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Accéder au site</a>
        </div>
        <hr style="border: 1px solid #eee; margin: 20px 0;">
        <p style="color: #666; font-size: 12px;">Cet email a été envoyé automatiquement. Merci de ne pas y répondre.</p>
      </div>
    `;

    await this.sendEmail(to, subject, html);
  }

  /**
   * Accusé de resoumission après refus (reprise #112) : dossier à nouveau en attente de validation.
   */
  async sendResoumissionPendingEmail(
    to: string,
    prenom: string,
    nom: string,
    numeroDossier: string,
  ): Promise<void> {
    const appName = this.configService.get<string>('app_name', "P'titsPas");
    const appUrl = this.configService.get<string>('app_url', 'https://app.ptits-pas.fr');

    const safe = (s: string) =>
      (s || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');

    const subject = `Votre dossier a été resoumis — ${safe(numeroDossier)}`;
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2 style="color: #4CAF50;">Bonjour ${safe(prenom)} ${safe(nom)},</h2>
        <p>Nous avons bien reçu la <strong>resoumission</strong> de votre dossier sur <strong>${safe(appName)}</strong>.</p>
        <p><strong>Numéro de dossier :</strong> ${safe(numeroDossier)}</p>
        <p>Votre dossier est de nouveau <strong>en attente de validation</strong> par notre équipe. Vous recevrez un email lorsqu'il aura été traité.</p>
        <div style="text-align: center; margin: 30px 0;">
          <a href="${appUrl}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Accéder au site</a>
        </div>
        <hr style="border: 1px solid #eee; margin: 20px 0;">
        <p style="color: #666; font-size: 12px;">Cet email a été envoyé automatiquement. Merci de ne pas y répondre.</p>
      </div>
    `;

    await this.sendEmail(to, subject, html);
  }

  /**
   * Email de refus de dossier avec lien reprise (token).
   * Ticket #110 – Refus sans suppression
   */
  async sendRefusEmail(
    to: string,
    prenom: string,
    nom: string,
    comment: string | undefined,
    token: string,
  ): Promise<void> {
    const appName = this.configService.get<string>('app_name', "P'titsPas");
    const appUrl = (this.configService.get<string>('app_url', 'https://app.ptits-pas.fr') || '').replace(/\/+$/, '');
    const repriseLink = `${appUrl}/reprise?token=${encodeURIComponent(token)}`;

    const safe = (s: string) =>
      (s || '')
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;');

    const subject = `Votre dossier – compléments demandés`;
    const commentText = safe(comment || 'Le gestionnaire vous demande de compléter votre dossier avant une nouvelle validation.');
    const html = `
      <div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
        <h2 style="color: #4CAF50;">Bonjour ${safe(prenom)} ${safe(nom)},</h2>
        <p>Votre dossier d'inscription sur <strong>${safe(appName)}</strong> n'a pas pu être validé en l'état.</p>
        <div style="background-color: #F4FBF5; border-left: 4px solid #4CAF50; padding: 12px 16px; margin: 20px 0;">
          <p style="margin: 0 0 8px 0;"><strong>Message du gestionnaire :</strong></p>
          <p style="margin: 0;">${commentText}</p>
        </div>
        <p>Vous pouvez corriger les éléments indiqués et soumettre à nouveau votre dossier en cliquant sur le lien ci-dessous.</p>
        <div style="text-align: center; margin: 30px 0;">
          <a href="${repriseLink}" style="background-color: #4CAF50; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; font-weight: bold;">Reprendre mon dossier</a>
        </div>
        <p style="color: #666; font-size: 12px;">Ce lien est valable 7 jours. Si vous n'avez pas demandé cette reprise, vous pouvez ignorer cet email.</p>
        <hr style="border: 1px solid #eee; margin: 20px 0;">
        <p style="color: #666; font-size: 12px;">Cet email a été envoyé automatiquement. Merci de ne pas y répondre.</p>
      </div>
    `;

    await this.sendEmail(to, subject, html);
  }

  /**
   * Ticket #127 — lien application `/reset-password?token=` (pas create-password).
   */
  async sendPasswordResetEmail(recipient: {
    email: string;
    prenom: string;
    nom: string;
    token: string;
  }): Promise<void> {
    const appName = this.configService.get<string>('app_name', "P'titsPas");
    const appUrl = (this.configService.get<string>('app_url', 'https://app.ptits-pas.fr') || '').replace(/\/+$/, '');
    const resetPasswordUrl = `${appUrl}/reset-password?token=${encodeURIComponent(recipient.token)}`;

    const filePath = join(__dirname, 'templates', 'password-reset.hbs');
    const source = readFileSync(filePath, 'utf8');
    const compiled = Handlebars.compile(source);
    const html = compiled({
      prenom: recipient.prenom || '',
      nom: recipient.nom || '',
      appName,
      resetPasswordUrl,
    }) as string;

    const subject = `${appName} — Réinitialisation de votre mot de passe`;
    await this.sendEmail(recipient.email, subject, html);
  }
}
