// Auto-hold stale unsettled manual-payment bookings.
//
// This job moves abandoned manual-payment bookings (bank transfer / TPago / cash)
// to 'on_hold' after HOLD_THRESHOLD_HOURS — bookings still in bare 'pending', i.e.
// the customer never clicked "I've paid" and no admin settled them. These are exempt
// from the 30-min auto-fail in bookingCleanup.ts, and under the capacity rule in
// lib/capacity.ts they hold no seat, so this sweep is pure list hygiene: it keeps
// dead checkouts out of the admin's pending queues.
//
// 'pending_approval' (customer claims they paid) is deliberately NOT swept: a
// claimed payment keeps its seat until an admin approves or rejects it — the admin
// UI surfaces aging claims instead of silently releasing them.
//
// The user receives no notification — they can recover via "I've paid", and an
// admin can approve/reactivate directly; every recovery path re-checks capacity.

import { and, eq, lt, inArray } from 'drizzle-orm';
import { db, dbAll, tickets, payments } from '../db/index.js';
import { getNow, toDbDate } from './utils.js';
import { getLock } from './stores/lock.js';
import { MANUAL_PAYMENT_PROVIDERS } from './paymentProviders.js';

function getThresholdMs(): number {
  const hours = parseInt(process.env.HOLD_THRESHOLD_HOURS || '72', 10);
  return (Number.isFinite(hours) && hours > 0 ? hours : 72) * 60 * 60 * 1000;
}

/**
 * Move stale unsettled manual payments (and their tickets) to 'on_hold'.
 * Covers only bare 'pending' payments on manual providers; 'pending_approval'
 * is never swept. Returns the number of payments put on hold.
 */
export async function sweepStaleApprovals(): Promise<number> {
  const cutoff = toDbDate(new Date(Date.now() - getThresholdMs()));

  const stale = await dbAll<{ ticketId: string | null; paymentId: string }>(
    (db as any)
      .select({
        ticketId: (payments as any).ticketId,
        paymentId: (payments as any).id,
      })
      .from(payments)
      .where(and(
        eq((payments as any).status, 'pending'),
        inArray((payments as any).provider, [...MANUAL_PAYMENT_PROVIDERS]),
        lt((payments as any).createdAt, cutoff)
      ))
  );

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

  const ticketIds = stale.map((s) => s.ticketId).filter((id): id is string => !!id);
  const paymentIds = stale.map((s) => s.paymentId);
  const now = getNow();

  await (db as any)
    .update(payments)
    .set({ status: 'on_hold', updatedAt: now })
    .where(inArray((payments as any).id, paymentIds));

  if (ticketIds.length > 0) {
    await (db as any)
      .update(tickets)
      .set({ status: 'on_hold' })
      .where(and(
        inArray((tickets as any).id, ticketIds),
        eq((tickets as any).status, 'pending')
      ));
  }

  console.log(`[HoldSweep] Put ${stale.length} stale unsettled manual payment(s) on hold.`);
  return stale.length;
}

let sweepTimer: ReturnType<typeof setInterval> | null = null;

/**
 * Start a periodic sweep of stale pending-approval payments. Each run is guarded by
 * a distributed lock so that, across multiple replicas, only one instance does the
 * work per interval.
 */
export function startHoldSweep(): void {
  const intervalMs = parseInt(process.env.HOLD_SWEEP_INTERVAL_MS || '900000', 10); // 15 min

  const run = () => {
    getLock()
      .withLock('sweep-hold-stale-approvals', Math.min(intervalMs, 60_000), () =>
        sweepStaleApprovals()
      )
      .catch((err) =>
        console.error('[HoldSweep] Run failed:', err?.message || err)
      );
  };

  // Run shortly after startup, then on the interval.
  setTimeout(run, 45_000).unref?.();
  sweepTimer = setInterval(run, intervalMs);
  sweepTimer.unref?.();
  console.log(`[HoldSweep] Scheduled every ${Math.round(intervalMs / 1000)}s`);
}

export function stopHoldSweep(): void {
  if (sweepTimer) {
    clearInterval(sweepTimer);
    sweepTimer = null;
  }
}
