/**
 * Email sender — thin wrapper around nodemailer.
 *
 * Uses SMTP_HOST/PORT/USER/PASS/FROM env vars. Falls back to a
 * console.log stub when SMTP_HOST is not configured so local dev
 * never crashes on send.
 */

import nodemailer from "nodemailer";

const SMTP_HOST = process.env.SMTP_HOST ?? "";
const SMTP_PORT = Number(process.env.SMTP_PORT || 587);
const SMTP_USER = process.env.SMTP_USER ?? "";
const SMTP_PASS = process.env.SMTP_PASS ?? "";
const SMTP_FROM = process.env.SMTP_FROM ?? "meSEO Reports <reports@meseo.io>";

let transporter: nodemailer.Transporter | null = null;

function getTransporter(): nodemailer.Transporter | null {
  if (transporter) return transporter;
  if (!SMTP_HOST) return null;
  transporter = nodemailer.createTransport({
    host: SMTP_HOST,
    port: SMTP_PORT,
    secure: SMTP_PORT === 465,
    auth: SMTP_USER ? { user: SMTP_USER, pass: SMTP_PASS } : undefined,
  });
  return transporter;
}

export interface SendResult {
  success: boolean;
  error?: string;
}

export interface SendOptions {
  /** Override the From header. Useful for white-label portal emails
   *  so clients see "Acme Agency <reports@meseo.io>" instead of the
   *  platform default. Pass a display name and we'll combine it with
   *  the platform's verified sender address. */
  fromName?: string;
  /** Full From header override. Only used when the caller controls
   *  a verified sender for this domain — otherwise fromName is safer
   *  (it keeps the platform address and just swaps the display name). */
  from?: string;
  /** Agency reply-to address. When set, clients who hit "reply" land
   *  in the agency's inbox instead of the platform's no-reply. */
  replyTo?: string;
}

/** Build the final From header. When the caller passes fromName, we
 *  combine it with the platform's verified sender address so SPF/DKIM
 *  stay intact; a full `from` override takes precedence when the caller
 *  has explicitly asked for one. */
function buildFromHeader(opts?: SendOptions): string {
  if (opts?.from) return opts.from;
  if (opts?.fromName) {
    // Extract bare address from SMTP_FROM ("Name <addr@host>" or "addr@host").
    const match = SMTP_FROM.match(/<([^>]+)>/);
    const bareAddr = match ? match[1] : SMTP_FROM;
    // Strip characters that would break the header if embedded raw.
    const safeName = opts.fromName.replace(/["\r\n]/g, "").trim();
    return safeName ? `"${safeName}" <${bareAddr}>` : SMTP_FROM;
  }
  return SMTP_FROM;
}

export async function sendEmail(
  to: string[],
  subject: string,
  html: string,
  opts?: SendOptions,
): Promise<SendResult> {
  const t = getTransporter();
  const fromHeader = buildFromHeader(opts);
  if (!t) {
    console.log(`[email-sender] SMTP not configured — would send as ${fromHeader}${opts?.replyTo ? ` (reply-to ${opts.replyTo})` : ""} to ${to.join(", ")}: "${subject}" (${html.length} chars)`);
    return { success: true }; // dev stub
  }

  try {
    await t.sendMail({
      from: fromHeader,
      to: to.join(", "),
      subject,
      html,
      ...(opts?.replyTo ? { replyTo: opts.replyTo } : {}),
    });
    return { success: true };
  } catch (err) {
    const msg = err instanceof Error ? err.message : "Send failed";
    console.error("[email-sender] send failed", msg);
    return { success: false, error: msg };
  }
}

/**
 * Computes the next send time for a schedule.
 */
export function computeNextSendAt(
  frequency: string,
  timeOfDay: string,
  dayOfWeek?: number | null,
  dayOfMonth?: number | null,
  after?: Date,
): Date {
  const now = after ?? new Date();
  const [hh, mm] = (timeOfDay || "08:00").split(":").map(Number);

  function setTime(d: Date): Date {
    const out = new Date(d);
    out.setUTCHours(hh, mm, 0, 0);
    return out;
  }

  if (frequency === "daily") {
    const today = setTime(now);
    return today > now ? today : setTime(new Date(now.getTime() + 86400000));
  }

  if (frequency === "weekly" || frequency === "biweekly") {
    const dow = dayOfWeek ?? 1; // default Monday
    const step = frequency === "biweekly" ? 14 : 7;
    let candidate = new Date(now);
    // Advance to next matching day-of-week
    const diff = ((dow - candidate.getUTCDay()) + 7) % 7 || step;
    candidate = new Date(candidate.getTime() + diff * 86400000);
    candidate = setTime(candidate);
    if (candidate <= now) candidate = new Date(candidate.getTime() + step * 86400000);
    return candidate;
  }

  if (frequency === "monthly") {
    const dom = Math.max(1, Math.min(28, dayOfMonth ?? 1));
    let candidate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), dom, hh, mm));
    if (candidate <= now) {
      candidate = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() + 1, dom, hh, mm));
    }
    return candidate;
  }

  // Fallback: tomorrow
  return setTime(new Date(now.getTime() + 86400000));
}
