// Backstop for the event → webhook fire-and-forget path.
//
// createEvent emits 'event.created' (sync) → an async listener calls
// handleEvent → schedules HTTP webhook delivery. On CF Workers the listener's
// microtask can lose the worker before its waitUntil registers if it fires
// from the last line of an HTTP handler (e.g. ingestVerifiedGooglePlayPurchase
// ends with `createEvent('subscription.started', ...).catch(...)` right before
// returning — the response flushes and runtime may stop draining new
// waitUntils). Result: events row exists with pending_webhooks>0 but no
// webhook_attempt is ever written, so downstream apps silently miss the
// notification.
//
// This cron rescans for pending events older than the realtime grace window
// and re-invokes handleEvent. Stripe et al ship the same belt-and-suspenders.

import { Op } from 'sequelize';

import logger from '../libs/logger';
import { eventQueue } from '../queues/event';
import { Event } from '../store/models/event';

const REALTIME_GRACE_SECONDS = 60;
// Each cron tick only enqueues, does NOT inline await handleEvent. CF Workers
// scheduled handler has a tight CPU budget (~30s) — inline handling N events
// blew past it after ~7 iterations and was cut off mid-batch, so the tail
// never got processed. Pushing to eventQueue lets the consumer (which has a
// looser per-message budget) work through them asynchronously.
//
// BATCH_LIMIT must stay small: even push-only, the scheduled handler awaits
// flushPendingJobs() at the end which await's each push's enqueue promise
// (D1 addJob + CF Queue send). 50 was too many — pushes never finished
// sending, jobs table never got rows, handleEvent never ran. 5 leaves
// generous headroom and we still drain the backlog over a few minutes.
const BATCH_LIMIT = 5;

export async function retryPendingEvents(): Promise<void> {
  const threshold = new Date(Date.now() - REALTIME_GRACE_SECONDS * 1000);
  const docs = await Event.findAll({
    where: {
      pending_webhooks: { [Op.gt]: 0 },
      created_at: { [Op.lt]: threshold },
    },
    attributes: ['id'],
    order: [['created_at', 'ASC']],
    limit: BATCH_LIMIT,
  });

  if (docs.length === 0) return;
  logger.info(`event.retry: enqueuing ${docs.length} pending events older than ${REALTIME_GRACE_SECONDS}s`);

  for (const doc of docs) {
    try {
      eventQueue.push({ id: doc.id, job: { eventId: doc.id }, persist: false });
    } catch (err: any) {
      logger.error('event.retry enqueue failed', { eventId: doc.id, error: err?.message });
    }
  }
}
