// SMTP transport for outbound mail (currently: magic-link sign-in
// links). Credentials come from /data/config/startos-config.json,
// which startos/main.ts keeps in sync with the StartOS System SMTP
// config via effects.getSystemSmtp({callback}).
//
// We poll the same JSON the rest of the server reads (via
// getConfigSnapshot()) and rebuild the nodemailer transport whenever
// the credentials change. Rebuilds are cheap — nodemailer's
// createTransport is synchronous and just stashes options. The actual
// SMTP connection is opened per-send (or pooled), not at transport
// creation.
//
// Only used in multi-tenant mode. Single mode never imports this.

import nodemailer from "nodemailer";
import { getConfigSnapshot } from "./config.js";

let cachedTransport = null;
let cachedFingerprint = ""; // last seen credentials as a stable string
let cachedFrom = "";

const POLL_MS = 3000;

// initSmtp(): kicks off the credentials-watch loop. Idempotent in
// effect (later calls are harmless), but you only need it once per
// process lifetime.
export function initSmtp() {
  refreshTransport().catch((err) => {
    console.warn("[smtp] initial refresh failed:", err);
  });
  setInterval(() => {
    refreshTransport().catch(() => {});
  }, POLL_MS);
}

async function refreshTransport() {
  const snap = await getConfigSnapshot();
  const host = snap.smtp_host || "";
  const port = parseInt(snap.smtp_port || 0, 10) || 0;
  const security = snap.smtp_security || "tls";
  const username = snap.smtp_username || "";
  const password = snap.smtp_password || "";
  const from = snap.smtp_from || "";

  // Fingerprint covers every field that affects the transport. If
  // nothing changed, leave the existing transport in place — avoids
  // tearing down a pooled connection on every poll tick.
  const fingerprint = [host, port, security, username, password, from].join(
    "\x1f",
  );
  if (fingerprint === cachedFingerprint) return;

  cachedFingerprint = fingerprint;
  cachedFrom = from;

  if (!host || !port) {
    if (cachedTransport) {
      cachedTransport.close?.();
      cachedTransport = null;
      console.log("[smtp] credentials cleared, transport closed");
    }
    return;
  }

  // security mapping:
  //   - "tls"     → implicit TLS (SMTPS, typically port 465). secure=true.
  //   - "starttls" → cleartext then STARTTLS (typically port 587). secure=false,
  //                  requireTLS=true so we refuse to silently fall back to plain.
  const isImplicitTls = security === "tls";
  const transportOpts = {
    host,
    port,
    secure: isImplicitTls,
    requireTLS: !isImplicitTls, // forces STARTTLS when secure=false
    auth: username ? { user: username, pass: password } : undefined,
  };

  try {
    const next = nodemailer.createTransport(transportOpts);
    if (cachedTransport) cachedTransport.close?.();
    cachedTransport = next;
    console.log(
      `[smtp] transport built host=${host} port=${port} security=${security} user=${username || "(none)"}`,
    );
  } catch (err) {
    console.warn("[smtp] createTransport failed:", err);
    cachedTransport = null;
  }
}

// sendMail({to, subject, text, html})
// Returns nodemailer's info object on success. Throws if the
// transport isn't configured — callers should treat that as "SMTP
// not set up yet, ask the operator to configure StartOS System SMTP."
export async function sendMail({ to, subject, text, html }) {
  if (!cachedTransport) {
    throw new Error("smtp_not_configured");
  }
  if (!cachedFrom) {
    throw new Error("smtp_from_not_set");
  }
  return await cachedTransport.sendMail({
    from: cachedFrom,
    to,
    subject,
    text,
    html,
  });
}

// True iff the transport is built and ready to send. The auth handlers
// use this to gate magic-link requests with a useful error rather than
// letting nodemailer's "no transport" leak through.
export function isSmtpReady() {
  return cachedTransport !== null && cachedFrom !== "";
}
