/**
 * SMTP Send Provider — default for new integrators.
 *
 * Uses `nodemailer` to deliver mail via any SMTP server. Configuration lives
 * in `appSettings` under the encrypted key `send_provider_config`:
 *
 * {
 *   "kind": "smtp",
 *   "host": "smtp.example.com",
 *   "port": 587,
 *   "secure": false,                       // true for port 465
 *   "auth": {
 *     "user": "user@example.com",
 *     "pass": "***"
 *   },
 *   "defaultFrom": "Mailforge <no-reply@example.com>",
 *   "auditBcc": "audit@example.com"        // optional
 * }
 *
 * Env-Var fallbacks (only used if Settings empty):
 *   SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASS, SMTP_FROM
 *
 * Phase B.2 wires this up to read from `getSecret("send_provider_config")`.
 * For now we use env-only.
 */

import nodemailer, { type Transporter } from 'nodemailer';
import type { SendProvider } from './interface';
import type { UserContext } from '@/core/types';
import type { SendMailInput, SendResult } from '@/adapters/types';
import { getSecret } from '@/core/settings';

interface SmtpConfig {
  kind?: 'smtp';
  host: string;
  port: number;
  secure: boolean;
  auth?: { user: string; pass: string };
  defaultFrom?: string;
  auditBcc?: string;
}

let cachedConfig: SmtpConfig | null = null;
let cachedTransport: Transporter | null = null;

function loadConfigFromEnv(): SmtpConfig | null {
  const host = process.env.SMTP_HOST?.trim();
  if (!host) return null;
  return {
    host,
    port: Number(process.env.SMTP_PORT ?? 587),
    secure: process.env.SMTP_PORT === '465',
    auth:
      process.env.SMTP_USER && process.env.SMTP_PASS
        ? { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS }
        : undefined,
    defaultFrom: process.env.SMTP_FROM?.trim() || undefined,
  };
}

function loadConfig(): SmtpConfig | null {
  try {
    const fromDb = getSecret('send_provider_config');
    if (fromDb) {
      const parsed = JSON.parse(fromDb) as SmtpConfig;
      // Defensive — only accept if it really is an SMTP config (the same key
      // also holds M365 configs when the m365 adapter is active).
      if ((!parsed.kind || parsed.kind === 'smtp') && parsed.host) {
        return parsed;
      }
    }
  } catch {
    // Fall through to env.
  }
  return loadConfigFromEnv();
}

function getTransport(): Transporter | null {
  const config = loadConfig();
  if (!config) return null;
  // Recreate transport if config changed.
  if (
    !cachedTransport ||
    !cachedConfig ||
    cachedConfig.host !== config.host ||
    cachedConfig.port !== config.port
  ) {
    cachedConfig = config;
    cachedTransport = nodemailer.createTransport({
      host: config.host,
      port: config.port,
      secure: config.secure,
      auth: config.auth,
    });
  }
  return cachedTransport;
}

function formatAddress(addr: { email: string; name?: string }): string {
  return addr.name ? `"${addr.name.replace(/"/g, '\\"')}" <${addr.email}>` : addr.email;
}

export const smtpProvider: SendProvider = {
  name: 'smtp',

  isConfigured() {
    return loadConfig() !== null;
  },

  async send(input: SendMailInput, _ctx?: UserContext): Promise<SendResult> {
    const transport = getTransport();
    const config = cachedConfig;
    if (!transport || !config) {
      return {
        ok: false,
        error: 'SMTP not configured — set SMTP_HOST or configure in Settings',
        errorKind: 'config',
      };
    }

    const bcc = [...(input.bcc ?? [])];
    if (config.auditBcc && !bcc.includes(config.auditBcc) && !input.to.includes(config.auditBcc)) {
      bcc.push(config.auditBcc);
    }

    try {
      const info = await transport.sendMail({
        from: formatAddress(input.from),
        to: input.to.join(', '),
        cc: input.cc?.join(', '),
        bcc: bcc.length ? bcc.join(', ') : undefined,
        replyTo: input.replyTo ? formatAddress(input.replyTo) : undefined,
        subject: input.subject,
        text: input.bodyText,
        html: input.bodyHtml,
        messageId: input.messageId,
        headers: input.headers,
      });
      return {
        ok: true,
        messageId: info.messageId ?? input.messageId,
      };
    } catch (err) {
      const code = (err as { code?: string } | null)?.code;
      const responseCode = (err as { responseCode?: number } | null)?.responseCode;
      let errorKind: SendResult['errorKind'] = 'unknown';
      if (code === 'ECONNREFUSED' || code === 'ETIMEDOUT' || code === 'ESOCKET') {
        errorKind = 'network';
      } else if (code === 'EAUTH') {
        errorKind = 'auth';
      } else if (responseCode && responseCode >= 400 && responseCode < 500) {
        errorKind = 'rejected';
      } else if (responseCode === 421 || responseCode === 450) {
        errorKind = 'rate-limit';
      }
      return {
        ok: false,
        error: err instanceof Error ? err.message : String(err),
        errorKind,
        httpStatus: responseCode,
      };
    }
  },

  async ping(senderEmail: string) {
    const transport = getTransport();
    if (!transport) {
      return { ok: false, error: 'SMTP not configured' };
    }
    try {
      // nodemailer's verify() runs EHLO + auth-check without sending.
      await transport.verify();
      // Sender-address check: SMTP servers don't expose a per-from-address
      // permission API, so we trust verify() for now. Adapters with stricter
      // policies (e.g. AWS SES verified-identities) can override.
      return { ok: true };
    } catch (err) {
      void senderEmail;
      return { ok: false, error: err instanceof Error ? err.message : String(err) };
    }
  },

  getQuotas() {
    // Conservative defaults. SMTP-per-server limits vary wildly; surface real
    // values via Settings if the integrator knows them.
    return { dailyLimit: 1000, perMinute: 10 };
  },
};
