// Self-serve subscription expiry reminders (multi-mode / cloud only).
//
// The relay owns each user's prepaid-period expiry (subscription source of
// truth); Recaps owns the email address + the recaps.cc SMTP transport. So
// a daily scan here asks the relay who's expiring (or just lapsed), maps
// user_id → users.email, and sends a reminder via the same sendMail() the
// magic-link flow uses. The subscription_reminders table dedups so each
// (user, period, kind) email goes out at most once; a renewal changes the
// expiry instant, which re-arms a fresh set of reminders for the new period.
//
// Self-gating: no-ops unless SMTP is configured, the public URL is set, and
// the relay is reachable. Safe to start unconditionally in multi mode.

import { getDb } from "./db.js";
import { sendMail, isSmtpReady } from "./smtp.js";
import { renderSubscriptionReminderEmail } from "./email-template.js";
import { getRelayExpiringSubscriptions } from "./providers/relay.js";
import { getConfigSnapshot } from "./config.js";

// Reminder thresholds, in days before expiry. Each maps to a distinct
// `kind` so a user gets one heads-up at each crossing (deduped by kind).
const UPCOMING_THRESHOLDS = [
  { days: 7, kind: "upcoming_7d" },
  { days: 1, kind: "upcoming_1d" },
];
const LAPSED_KIND = "lapsed";
// Only email recently-lapsed users; after this they age out of the relay
// window (and we never re-send the same period's lapsed notice anyway).
const LAPSED_WINDOW_DAYS = 3;

const SCAN_INTERVAL_MS = 12 * 60 * 60 * 1000; // twice a day
const BOOT_DELAY_MS = 90 * 1000; // first scan ~90s after boot

let scanning = false;
let scheduled = false;

// Decide which single reminder kind (if any) applies to a subscription
// right now. Pure — exported for testing. `sub` is a relay row:
// { tier, expires_at, expired, days_left }.
export function reminderKindFor(
  sub,
  { upcoming = UPCOMING_THRESHOLDS, lapsedWindowDays = LAPSED_WINDOW_DAYS } = {},
) {
  if (!sub || typeof sub.days_left !== "number") return null;
  const daysLeft = sub.days_left;
  if (sub.expired) {
    // Recently lapsed → a single lapsed notice (days_left is <= 0).
    return daysLeft >= -lapsedWindowDays ? LAPSED_KIND : null;
  }
  // Upcoming: smallest threshold the sub has crossed wins, so daysLeft=1
  // sends 'upcoming_1d' while daysLeft in (1, 7] sends 'upcoming_7d'.
  const sorted = [...upcoming].sort((a, b) => a.days - b.days);
  for (const t of sorted) {
    if (daysLeft <= t.days) return t.kind;
  }
  return null;
}

function alreadySent(db, userId, periodIso, kind) {
  return !!db
    .prepare(
      "SELECT 1 FROM subscription_reminders WHERE user_id=? AND period_expires_at=? AND kind=?",
    )
    .get(userId, periodIso, kind);
}

function recordSent(db, userId, periodIso, kind) {
  db.prepare(
    "INSERT OR IGNORE INTO subscription_reminders (user_id, period_expires_at, kind, sent_at) VALUES (?,?,?,?)",
  ).run(userId, periodIso, kind, Date.now());
}

function maskEmail(email) {
  return String(email).replace(/^(.).*(@.*)$/, "$1***$2");
}

// One scan pass. Returns a small summary object; never throws (logs +
// returns a {skipped} reason instead) so the scheduler stays alive.
export async function runReminderScan({ force = false } = {}) {
  if (scanning && !force) return { skipped: "already_running" };
  scanning = true;
  try {
    if (!isSmtpReady()) return { skipped: "smtp_not_ready" };
    const snap = await getConfigSnapshot();
    const publicUrl = (snap.recap_public_url || "").trim().replace(/\/$/, "");
    if (!publicUrl) return { skipped: "public_url_not_set" };

    const maxDays = Math.max(1, ...UPCOMING_THRESHOLDS.map((t) => t.days));
    const report = await getRelayExpiringSubscriptions({
      withinDays: maxDays,
      lapsedDays: LAPSED_WINDOW_DAYS,
    });
    if (!report || !Array.isArray(report.subscriptions)) {
      return { skipped: "relay_unavailable" };
    }

    const db = getDb();
    const manageUrl = `${publicUrl}/?renew=1`;
    let sent = 0;
    let skipped = 0;
    for (const sub of report.subscriptions) {
      const kind = reminderKindFor(sub);
      if (!kind) {
        skipped++;
        continue;
      }
      const periodIso = sub.expires_at;
      if (!periodIso || alreadySent(db, sub.user_id, periodIso, kind)) {
        skipped++;
        continue;
      }
      const u = db
        .prepare("SELECT email FROM users WHERE id=?")
        .get(sub.user_id);
      const email = (u?.email || "").trim();
      if (!email) {
        // No local user for this id (e.g. another instance's tenant) — skip.
        skipped++;
        continue;
      }
      const message = renderSubscriptionReminderEmail({
        brandName: "Recaps",
        tier: sub.tier,
        expiresAt: sub.expires_at,
        daysLeft: sub.days_left,
        kind,
        manageUrl,
      });
      try {
        await sendMail({
          to: email,
          subject: message.subject,
          text: message.text,
          html: message.html,
        });
        recordSent(db, sub.user_id, periodIso, kind);
        sent++;
        console.log(
          `[reminders] sent ${kind} to ${maskEmail(email)} (${sub.tier}, ${sub.days_left}d)`,
        );
      } catch (err) {
        // Don't record on failure → retried next scan.
        console.warn(
          `[reminders] sendMail failed for ${sub.user_id}: ${err?.message || err}`,
        );
        skipped++;
      }
    }
    if (sent) {
      console.log(`[reminders] scan complete: ${sent} sent, ${skipped} skipped`);
    }
    return { sent, skipped };
  } catch (err) {
    console.warn(`[reminders] scan error: ${err?.message || err}`);
    return { skipped: "error", error: err?.message || String(err) };
  } finally {
    scanning = false;
  }
}

// Start the daily-ish scan loop. Idempotent. Self-gates inside the scan,
// so it's safe to call whenever multi mode boots.
export function startReminderScheduler() {
  if (scheduled) return;
  scheduled = true;
  setTimeout(() => {
    runReminderScan().catch(() => {});
  }, BOOT_DELAY_MS);
  setInterval(() => {
    runReminderScan().catch(() => {});
  }, SCAN_INTERVAL_MS);
  console.log("[reminders] expiry-reminder scheduler started");
}
