// Self-contained SMTP transport for server-to-server (queued) email.
//
// Extracted from the proven conversation in `send-smtp-email` so the automated
// email dispatcher (`process-email-queue`) can send through the same SMTP path
// the app already uses — no third-party email API required.
//
// `sendSmtpMessage` throws on any failure so the caller's existing retry / DLQ
// logic can record and reschedule the message.

export type SmtpSender = {
  host: string;
  port: number;
  username: string;
  password: string;
  use_tls?: boolean;
  use_ssl?: boolean;
  fromEmail: string;
  fromName?: string;
};

export type SmtpMessage = {
  to: string;
  subject: string;
  html: string;
  text?: string;
  /** When set, adds List-Unsubscribe + one-click headers (bulk deliverability). */
  unsubscribeUrl?: string;
};

const SMTP_CONNECT_TIMEOUT_MS = 20000;
const SMTP_COMMAND_TIMEOUT_MS = 30000;

function withTimeout<T>(promise: Promise<T>, timeoutMs: number, message: string): Promise<T> {
  let timeoutId: number | undefined;
  return new Promise<T>((resolve, reject) => {
    timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs);
    promise.then(resolve).catch(reject).finally(() => {
      if (timeoutId !== undefined) clearTimeout(timeoutId);
    });
  });
}

function sanitizeHeaderValue(value: string): string {
  return String(value ?? "").replace(/[\r\n]+/g, " ").trim();
}

function formatAddressHeader(email: string, displayName?: string): string {
  const safeEmail = sanitizeHeaderValue(email);
  const safeName = sanitizeHeaderValue(displayName || "");
  if (!safeName) return safeEmail;
  const escaped = safeName.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
  const needsQuotes = /[",;<>@\[\]\(\):]/.test(escaped);
  return needsQuotes ? `"${escaped}" <${safeEmail}>` : `${escaped} <${safeEmail}>`;
}

function toBase64Utf8(value: string): string {
  const bytes = new TextEncoder().encode(value);
  let binary = "";
  for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
  return btoa(binary);
}

function chunkBase64(value: string, lineLength = 76): string {
  const lines: string[] = [];
  for (let i = 0; i < value.length; i += lineLength) lines.push(value.slice(i, i + lineLength));
  return lines.join("\r\n");
}

function dotStuff(content: string): string {
  return content.replace(/\r?\n/g, "\r\n").replace(/(^|\r\n)\./g, "$1..");
}

function htmlToPlainText(html: string): string {
  return html
    .replace(/<\s*br\s*\/?>/gi, "\n")
    .replace(/<\/(p|div|h1|h2|h3|h4|h5|h6|li|tr|section)\s*>/gi, "\n")
    .replace(/<style[\s\S]*?<\/style>/gi, "")
    .replace(/<script[\s\S]*?<\/script>/gi, "")
    .replace(/<[^>]+>/g, "")
    .replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">")
    .replace(/\r\n/g, "\n").replace(/\n{3,}/g, "\n\n").trim();
}

export async function sendSmtpMessage(sender: SmtpSender, msg: SmtpMessage): Promise<string> {
  const { host, password, username } = sender;
  const port = Number(sender.port || 587);
  const use_ssl = sender.use_ssl ?? port === 465;
  const use_tls = sender.use_tls ?? port === 587;

  if (!host || !username || !password || !sender.fromEmail) {
    throw new Error("Incomplete SMTP sender configuration (host, username, password, fromEmail required)");
  }

  const to = sanitizeHeaderValue(msg.to);
  if (!to) throw new Error("Missing recipient");

  const fromHeader = formatAddressHeader(sender.fromEmail, sender.fromName);
  const subject = sanitizeHeaderValue(msg.subject);
  const html = String(msg.html ?? "").trim() || `<!DOCTYPE html><html><body></body></html>`;
  const text = msg.text && msg.text.trim() ? msg.text : (htmlToPlainText(html) || subject);
  const domain = sender.fromEmail.includes("@") ? sender.fromEmail.split("@")[1] : host;
  const messageId = `<${crypto.randomUUID()}@${domain}>`;
  const boundary = `----=_Part_${crypto.randomUUID().replace(/-/g, "")}`;

  const headers: string[] = [
    `From: ${fromHeader}`,
    `To: ${to}`,
    `Reply-To: ${sanitizeHeaderValue(sender.fromEmail)}`,
    `Subject: ${subject}`,
    `Date: ${new Date().toUTCString()}`,
    `Message-ID: ${messageId}`,
    `MIME-Version: 1.0`,
  ];
  if (msg.unsubscribeUrl) {
    headers.push(`List-Unsubscribe: <${sanitizeHeaderValue(msg.unsubscribeUrl)}>`);
    headers.push(`List-Unsubscribe-Post: List-Unsubscribe=One-Click`);
  }
  headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);

  let mime = headers.join("\r\n") + "\r\n\r\n";
  mime += `--${boundary}\r\nContent-Type: text/plain; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n`;
  mime += chunkBase64(toBase64Utf8(text)) + "\r\n\r\n";
  mime += `--${boundary}\r\nContent-Type: text/html; charset=UTF-8\r\nContent-Transfer-Encoding: base64\r\n\r\n`;
  mime += chunkBase64(toBase64Utf8(html)) + "\r\n\r\n";
  mime += `--${boundary}--\r\n`;

  const conn: Deno.Conn = use_ssl
    ? await withTimeout(Deno.connectTls({ hostname: host, port }), SMTP_CONNECT_TIMEOUT_MS, `Timed out connecting (SSL) to ${host}:${port}`)
    : await withTimeout(Deno.connect({ hostname: host, port }), SMTP_CONNECT_TIMEOUT_MS, `Timed out connecting to ${host}:${port}`);

  const encoder = new TextEncoder();
  const decoder = new TextDecoder();

  const read = async (c: Deno.Conn): Promise<string> => {
    const buf = new Uint8Array(4096);
    const n = await withTimeout(c.read(buf), SMTP_COMMAND_TIMEOUT_MS, `Timed out awaiting SMTP response from ${host}`);
    if (n === null) throw new Error("SMTP server closed the connection unexpectedly");
    return decoder.decode(buf.subarray(0, n));
  };
  const cmd = async (c: Deno.Conn, command: string): Promise<string> => {
    await withTimeout(c.write(encoder.encode(command + "\r\n")), SMTP_COMMAND_TIMEOUT_MS, `Timed out sending SMTP command to ${host}`);
    return await read(c);
  };

  try {
    await read(conn);
    let active: Deno.Conn = conn;
    const ehlo = await cmd(active, "EHLO localhost");

    if (!use_ssl && use_tls && ehlo.includes("STARTTLS")) {
      const st = await cmd(active, "STARTTLS");
      if (!st.startsWith("220")) throw new Error(`STARTTLS not available: ${st.trim()}`);
      active = await withTimeout(Deno.startTls(active as Deno.TcpConn, { hostname: host }), SMTP_COMMAND_TIMEOUT_MS, `Timed out on STARTTLS upgrade for ${host}`);
      await cmd(active, "EHLO localhost");
    }

    await cmd(active, "AUTH LOGIN");
    await cmd(active, btoa(username));
    const authResp = await cmd(active, btoa(password));
    if (!authResp.startsWith("235")) throw new Error(`SMTP authentication failed for ${username}: ${authResp.trim()}`);

    const mailFrom = await cmd(active, `MAIL FROM:<${sanitizeHeaderValue(sender.fromEmail)}>`);
    if (!mailFrom.startsWith("250")) throw new Error(`MAIL FROM rejected: ${mailFrom.trim()}`);

    const rcpt = await cmd(active, `RCPT TO:<${to}>`);
    if (!rcpt.startsWith("250") && !rcpt.startsWith("251")) throw new Error(`RCPT TO rejected for ${to}: ${rcpt.trim()}`);

    const dataResp = await cmd(active, "DATA");
    if (!dataResp.startsWith("354")) throw new Error(`DATA rejected: ${dataResp.trim()}`);

    const payload = dotStuff(mime) + "\r\n.\r\n";
    const CHUNK = 4096;
    for (let off = 0; off < payload.length; off += CHUNK) {
      await withTimeout(active.write(encoder.encode(payload.slice(off, off + CHUNK))), SMTP_COMMAND_TIMEOUT_MS, `Timed out sending message body to ${host}`);
    }
    const finalResp = await read(active);
    try { await cmd(active, "QUIT"); } catch (_) { /* ignore */ }
    if (!finalResp.startsWith("250")) throw new Error(`Message rejected by ${host}: ${finalResp.trim()}`);
    return finalResp.trim();
  } finally {
    try { conn.close(); } catch (_) { /* already closed */ }
  }
}
