import { ok, err, type CommandContext } from "@tailor-platform/erp-kit/core";
import type { Transaction } from "../generated/kysely-tailordb";
import { InvalidRetentionTtlError } from "../lib/errors.generated";

const TOMBSTONED = "TOMBSTONED";
const SYSTEM_SENTINEL = "system";
const DEFAULT_TTL_DAYS = 90;
const MS_PER_DAY = 24 * 60 * 60 * 1000;

export interface RunNotificationAuditRetentionSweepInput {
  ttlDays?: number;
}

/**
 * Anonymizes audit and destination-delivery-log rows older than the retention cutoff (default 90 days) via set-based updates. Audit-permission authorized; never deletes.
 */
export async function run(
  db: Transaction,
  input: RunNotificationAuditRetentionSweepInput,
  ctx: CommandContext,
) {
  void ctx;
  const ttlDays = input.ttlDays ?? DEFAULT_TTL_DAYS;

  // ttlDays must be a positive integer: zero, negative, fractional, or NaN
  // values would push the cutoff to (or past) "now" and anonymize live rows.
  if (!Number.isInteger(ttlDays) || ttlDays < 1) {
    return err(new InvalidRetentionTtlError(`ttlDays=${String(ttlDays)}`));
  }

  const now = new Date();
  const cutoff = new Date(now.getTime() - ttlDays * MS_PER_DAY);

  // PERSONAL-track sweep, set-based. Two statements so that the system
  // sentinel keeps its occurredBy while user-driven rows are tombstoned.
  // Both exclude already-anonymized rows, so repeated sweep passes do not
  // pick them up again. Failures abort the transaction and surface to the
  // scheduler instead of being silently swallowed per row.
  //
  // 1) User-driven rows past TTL: tombstone occurredBy and clear errorDetail.
  const tombstonedRows = await db
    .updateTable("NotificationDeliveryAudit")
    .set({ errorDetail: null, occurredBy: TOMBSTONED, updatedAt: now })
    .where("occurredAt", "<", cutoff)
    .where("occurredBy", "not in", [SYSTEM_SENTINEL, TOMBSTONED])
    .returningAll()
    .execute();

  // 2) System / already-tombstoned rows past TTL still carrying an
  //    errorDetail: clear it, keep occurredBy as-is (the system sentinel is
  //    not a user identifier).
  const clearedRows = await db
    .updateTable("NotificationDeliveryAudit")
    .set({ errorDetail: null, updatedAt: now })
    .where("occurredAt", "<", cutoff)
    .where("occurredBy", "in", [SYSTEM_SENTINEL, TOMBSTONED])
    .where("errorDetail", "is not", null)
    .returningAll()
    .execute();

  const rowsAnonymized = tombstonedRows.length + clearedRows.length;

  // DESTINATION-track sweep: clear the redacted provider response and failure
  // reason on DestinationDeliveryLog rows past TTL, preserving the structural
  // shell. Same "anonymize, do not delete" stance as the PERSONAL track; the
  // free-form-fields-already-null predicate keeps anonymized rows out of the
  // sweep on subsequent passes.
  const destinationRows = await db
    .updateTable("DestinationDeliveryLog")
    .set({ providerResponse: null, failureReason: null, updatedAt: now })
    .where("attemptedAt", "<", cutoff)
    .where((eb) =>
      eb.or([eb("providerResponse", "is not", null), eb("failureReason", "is not", null)]),
    )
    .returningAll()
    .execute();

  return ok({ rowsAnonymized, destinationRowsAnonymized: destinationRows.length });
}
