import type { Kysely } from "kysely";
import { logError, logInfo } from "@/lib/logger";
import { getMailQueue } from "./mail";

/**
 * The 30-day follow-up mail for retired accounts.
 *
 * Polled from the console's own database rather than scheduled on the broker. A
 * delayed AMQP message thirty days out survives neither a queue purge nor a change
 * of mind, and there is no way to look at one and ask whether it is still due; a row
 * with a due date answers both, and the sweep is a single indexed query.
 *
 * Runs hourly. The window is thirty days, so an hour of lateness is not a
 * difference anyone can perceive, and checking more often would only add queries.
 *
 * Recipients are the addresses the first mail actually went to, snapshotted on the
 * record. Re-deriving them now would be wrong twice over: the retirement may have
 * deactivated those member rows, and the follow-up is meant for whoever was told the
 * first time.
 */

/** How many records one pass will send for, so a backlog cannot flood the queue. */
const BATCH_LIMIT = 200;

export async function processAccountDeletionFollowups(
  db: Kysely<any>,
): Promise<void> {
  try {
    const due = await db
      .selectFrom("account_deletion_records" as any)
      .select([
        "id",
        "account_number",
        "account_name",
        "status_name",
        "recipients",
        "created_at",
        "followup_template_id",
      ])
      .where("followup_edm_status", "=", "pending")
      .where("followup_due_at", "<=", new Date())
      .orderBy("followup_due_at", "asc")
      .limit(BATCH_LIMIT)
      .execute();

    if (due.length === 0) return;

    const queue = getMailQueue();
    let sent = 0;

    for (const record of due) {
      const recipients: string[] = Array.isArray(record.recipients)
        ? record.recipients
        : JSON.parse(record.recipients ?? "[]");

      if (recipients.length === 0) {
        await markOutcome(db, record.id, {
          status: "skipped",
          error: "No recipients were recorded for this account",
        });
        continue;
      }

      /*
       * The template the deletion recorded, and no fallback.
       *
       * Resolved when the deletion was applied — the region's confirmation letter,
       * or an explicit override — so a row without one predates that and there is
       * nothing to guess. Sending the wrong region's letter is worse than sending
       * none, so this refuses rather than substituting.
       *
       * Marked skipped rather than left pending: a pending row is re-read every hour
       * forever, and the record would never say why nothing was sent.
       */
      const templateID: string | null = record.followup_template_id;
      if (!templateID) {
        await markOutcome(db, record.id, {
          status: "skipped",
          error:
            "This record does not name a follow-up template, so there is nothing to send",
        });
        continue;
      }

      try {
        await queue.publish(templateID, {
          to: recipients.map((email) => ({ email })),
          templateData: {
            account_number: record.account_number,
            account_name: record.account_name ?? "",
            status: record.status_name,
            deleted_on: new Date(record.created_at).toISOString().slice(0, 10),
          },
        });
        await markOutcome(db, record.id, { status: "sent" });
        sent += 1;
      } catch (err: any) {
        /*
         * Left `failed`, not returned to `pending`.
         *
         * A row that goes back to pending is retried every hour forever, and a
         * permanently bad address would keep the sweep busy and the log noisy. A
         * failed record is visible on the page and can be re-queued deliberately.
         */
        await markOutcome(db, record.id, {
          status: "failed",
          error: err?.message ?? String(err),
        });
        logError(
          `[AccountDeletionFollowup] Failed for ${record.account_number}: ${err?.message ?? err}`,
        );
      }
    }

    logInfo(
      `[AccountDeletionFollowup] ${due.length} due, ${sent} published to the SMTP queue`,
    );
  } catch (err: any) {
    logError(
      `[AccountDeletionFollowup] processAccountDeletionFollowups error: ${err?.message ?? err}`,
    );
  }
}

async function markOutcome(
  db: Kysely<any>,
  recordID: string,
  outcome: { status: "sent" | "failed" | "skipped"; error?: string },
): Promise<void> {
  await db
    .updateTable("account_deletion_records" as any)
    .set({
      followup_edm_status: outcome.status,
      followup_edm_at: new Date(),
      followup_edm_error: outcome.error ?? null,
      updated_at: new Date(),
    })
    .where("id", "=", recordID)
    .execute();
}
