/**
 * Cron: Process Outbox - runs every 5 minutes alongside personal-deal-timeout.
 *
 * Reads unprocessed events from the `outbox` table and dispatches each to the
 * appropriate handler (email, push, club membership, settlement accounting).
 *
 * Delivery guarantee: at-least-once. Each event is locked with
 * `FOR UPDATE SKIP LOCKED` before processing so concurrent Workers instances
 * cannot double-process the same event.
 *
 * Retry policy: up to 3 attempts, with a 5-minute back-off between attempts.
 * After 3 failures the event is abandoned (retry_count = 3, last_error records
 * the final failure reason).
 *
 * Dispatch logic lives in src/server/workflows/outbox/dispatcher.ts (shared with
 * the Cloudflare Queue consumer at src/server/platform/queues/outbox-consumer.ts).
 * Do not inline handlers here.
 */

import { createDbService } from '@/server/services/db.js';
import { withSentry } from '@/server/observability/with-sentry';
import { captureCaught } from '@/server/observability/capture.server';
import {
  getPendingOutboxEvents,
  markOutboxEventProcessed,
  markOutboxEventFailed,
  outboxRetryLimitForError,
} from '../db/queries/outbox.js';
import { dispatchOutboxRow } from '../workflows/outbox/dispatcher.js';
import type { CronEnv } from './deal-expiry.js';

// ---------------------------------------------------------------------------
// Runner
// ---------------------------------------------------------------------------

export const runProcessOutbox = withSentry(
  async function runProcessOutbox(env: CronEnv): Promise<void> {
    const db = env.db ?? createDbService({ DATABASE_URL: env.DATABASE_URL });
    const events = await getPendingOutboxEvents(db, 50);

    const dispatchEnv = {
      DATABASE_URL: env.DATABASE_URL,
      PAYMENT_PROVIDER: env.PAYMENT_PROVIDER,
      RESEND_API_KEY: env.RESEND_API_KEY,
      RESEND_FROM_EMAIL: (env as unknown as { RESEND_FROM_EMAIL?: string }).RESEND_FROM_EMAIL,
      VAPID_PUBLIC_KEY: env.VAPID_PUBLIC_KEY,
      VAPID_PRIVATE_KEY: env.VAPID_PRIVATE_KEY,
      VAPID_SUBJECT: env.VAPID_SUBJECT,
      PII_KEY: env.PII_KEY,
      GOOGLE_API_KEY: env.GOOGLE_API_KEY,
      // Non-prod email mock sink — runtime env (cloudflare:workers proxy) carries
      // both; CronEnv doesn't declare them, so cast like RESEND_FROM_EMAIL above.
      // Without these, email handlers can't reach resend.ts's mock branch and
      // preview really sends transactional mail. See design addendum 2026-06-17.
      ENVIRONMENT: (env as unknown as { ENVIRONMENT?: string }).ENVIRONMENT,
      EMAIL_MOCK_DB: (env as unknown as { EMAIL_MOCK_DB?: D1Database }).EMAIL_MOCK_DB,
      TOPIC_DO: env.TOPIC_DO,
      CACHE_EPOCH_DO: (env as CronEnv & { CACHE_EPOCH_DO: DurableObjectNamespace }).CACHE_EPOCH_DO,
      PUBLIC_SITE_URL: env.PUBLIC_SITE_URL,
      strict: true,
    };

    for (const event of events) {
      try {
        await dispatchOutboxRow(dispatchEnv, event);
        await markOutboxEventProcessed(db, event.id);
      } catch (err) {
        const message = err instanceof Error ? err.message : String(err);
        console.error(`[outbox] event ${event.id} (${event.eventType}) failed:`, message);
        captureCaught(err, {
          scope: 'cron.process-outbox',
          severity: 'error',
          extra: { eventId: event.id, eventType: event.eventType },
        });
        // Cap sets dead_at terminal state surfaced by outbox_abandoned check.
        await markOutboxEventFailed(db, event.id, message, outboxRetryLimitForError(err));
      }
    }
  },
  { name: 'cron.process-outbox', kind: 'cron' },
);
