// apps/web/src/server/cron/affiliate-tick.ts
/*
 * Affiliate tick cron runner.
 *
 * Wired into the 30-min cron bucket via dispatchCron.
 * Also callable via POST /api/internal/affiliate/tick (Bearer CRON_SECRET).
 *
 * Jobs run on schedule:
 *   Every 30m:  maturation sweep.
 *   Minute === 0:       analytics rollup (deferred T22 — stub no-op).
 *   Hour % 6 === 0 && Minute === 0: auto-suspend pass (deferred T21 — stub no-op).
 *   Hour === 3 && Minute === 0: referral graph cycle scan.
 */

import { createDbService } from '@/server/services/db.js';
import { withSentry } from '@/server/observability/with-sentry';
import { captureCaught } from '@/server/observability/capture.server';
import { sweepMaturation } from '../referrals/maturation.js';
import { runWithdrawableSweep } from '../referrals/withdrawable-sweep.js';
import { getReferralSettings } from '../referrals/settings.js';
import { runAnalyticsRollup } from '../referrals/analytics-rollup.js';
import { runAutoSuspend } from '../referrals/auto-suspend.js';
import { runShareAnalyticsRollup } from '../sharing/analytics-rollup.js';
import { runReferralGraphScan } from '../referrals/graph-scan.js';
import type { CronEnv } from './deal-expiry.js';

export const runAffiliateTick = withSentry(
  async (env: CronEnv): Promise<void> => {
    const db = env.db ?? createDbService({ DATABASE_URL: env.DATABASE_URL });
    const now = new Date();
    const hour = now.getUTCHours();
    const minute = now.getUTCMinutes();

    // Always: maturation sweep.
    try {
      const result = await sweepMaturation(db);
      if (result.promoted > 0 || result.recomputed > 0) {
        console.warn(
          `[affiliate-tick] maturation promoted=${result.promoted} recomputed=${result.recomputed}`,
        );
      }
    } catch (err) {
      captureCaught(err, { scope: 'server.cron.affiliate-tick.maturation', severity: 'error' });
    }

    // Always: withdrawable balance sweep (runs after maturation so newly promoted rows are included).
    try {
      const settings = await getReferralSettings(db);
      const wsResult = await runWithdrawableSweep(db, settings.disputeWindowDays);
      if (wsResult.updated > 0) {
        console.warn(`[affiliate-tick] withdrawable sweep updated=${wsResult.updated}`);
      }
    } catch (err) {
      captureCaught(err, {
        scope: 'server.cron.affiliate-tick.withdrawable-sweep',
        severity: 'error',
      });
    }

    // Hourly: analytics rollup (T22 stub — skips).
    if (minute === 0) {
      try {
        const cfEnv = env as unknown as Record<string, string | undefined>;
        await runAnalyticsRollup(db, {
          CF_ACCOUNT_ID: cfEnv['CF_ACCOUNT_ID'],
          CF_AE_API_TOKEN: cfEnv['CF_AE_API_TOKEN'],
        });
      } catch (err) {
        captureCaught(err, { scope: 'server.cron.affiliate-tick.rollup', severity: 'warning' });
      }

      // Share analytics rollup — same hourly gate, no separate cron needed.
      try {
        const cfEnv = env as unknown as Record<string, string | undefined>;
        await runShareAnalyticsRollup(db, {
          CF_ACCOUNT_ID: cfEnv['CF_ACCOUNT_ID'],
          CF_AE_API_TOKEN: cfEnv['CF_AE_API_TOKEN'],
        });
      } catch (err) {
        captureCaught(err, {
          scope: 'server.cron.affiliate-tick.share-rollup',
          severity: 'warning',
        });
      }
    }

    // Every 6 hours: auto-suspend pass (T21 stub — skips).
    if (hour % 6 === 0 && minute === 0) {
      try {
        await runAutoSuspend(db);
      } catch (err) {
        captureCaught(err, {
          scope: 'server.cron.affiliate-tick.auto-suspend',
          severity: 'warning',
        });
      }
    }

    // Daily at 03:00 UTC: referral graph cycle scan.
    if (hour === 3 && minute === 0) {
      try {
        const gsResult = await runReferralGraphScan(db);
        console.warn(
          `[affiliate-tick] graph-scan rings=${gsResult.ringsFound} events=${gsResult.eventsInserted}`,
        );
      } catch (err) {
        captureCaught(err, {
          scope: 'server.cron.affiliate-tick.graph-scan',
          severity: 'error',
        });
      }
    }
  },
  { name: 'affiliate-tick', kind: 'cron' },
);
