/**
 * Outbox stale-`sending` sweep — the retry backstop noted in Handoff 03.
 *
 * The Mac client claims a row (queued → sending) *before* sending so a
 * concurrent tick can't double-send. If its result POST then fails, the row is
 * stranded in `sending` and `listDue` (queued-only) never refetches it. This
 * periodic sweep reclaims any row stuck in `sending` past `staleAfterMs`,
 * re-queueing it while attempts remain (else hard-failing it) so the drain can
 * pick it up again.
 *
 * Call `startOutboxSweep()` once at app startup; it runs for the process
 * lifetime. A single tick can never overlap itself, and a failed tick is logged
 * and retried on the next interval (never throws into the timer).
 */

import { logger } from '@/shared/logger';
import { getDb } from '@/shared/db';
import { requeueStaleSending } from '@/entities/outbox';

/** How often to sweep. */
const DEFAULT_INTERVAL_MS = 5 * 60_000;
/** A `sending` row older than this (no result reported) is considered wedged. */
const DEFAULT_STALE_AFTER_MS = 10 * 60_000;

export interface OutboxSweepConfig {
  intervalMs?: number;
  staleAfterMs?: number;
}

let timer: ReturnType<typeof setInterval> | null = null;
let sweeping = false;

/** One sweep pass. Exported for tests / manual invocation. Never throws. */
export async function sweepOnce(staleAfterMs: number = DEFAULT_STALE_AFTER_MS): Promise<number> {
  const cutoff = new Date(Date.now() - staleAfterMs).toISOString();
  try {
    const reclaimed = await requeueStaleSending(getDb(), cutoff);
    if (reclaimed.length > 0) {
      const requeued = reclaimed.filter((r) => r.status === 'queued').length;
      const failed = reclaimed.length - requeued;
      logger.warn('outbox-sweep: reclaimed stale sending rows', {
        total: reclaimed.length,
        requeued,
        hardFailed: failed,
      });
    }
    return reclaimed.length;
  } catch (err) {
    logger.error('outbox-sweep: pass failed', {
      err: err instanceof Error ? err.message : String(err),
    });
    return 0;
  }
}

export function startOutboxSweep(config: OutboxSweepConfig = {}): void {
  const intervalMs = config.intervalMs ?? DEFAULT_INTERVAL_MS;
  const staleAfterMs = config.staleAfterMs ?? DEFAULT_STALE_AFTER_MS;
  if (timer) return;

  logger.info('outbox-sweep: starting', { intervalMs, staleAfterMs });
  timer = setInterval(() => {
    if (sweeping) return; // never overlap a slow pass
    sweeping = true;
    void sweepOnce(staleAfterMs).finally(() => {
      sweeping = false;
    });
  }, intervalMs);
  // Don't keep the process alive solely for the sweep.
  if (typeof timer === 'object' && timer && 'unref' in timer) {
    (timer as { unref: () => void }).unref();
  }
}

export function stopOutboxSweep(): void {
  if (timer) {
    clearInterval(timer);
    timer = null;
    logger.info('outbox-sweep: stopped');
  }
}
