/**
 * Cron: Deal Expiry - runs every minute.
 *
 * FDS §6.5: When window_end has passed and redemption_status = UNREDEEMED:
 *   - Transition purchase.redemption_status to EXPIRED
 *   - Split funds: 50% Multideal, 50% vendor (recorded; actual payout in settlement)
 *   - Technical review option made available
 *
 * Also transitions deals with window_end < now to EXPIRED state if they are
 * still ACTIVE or PAUSED.
 */

import { createDbService } from '@/server/services/db.js';
import { and, inArray, eq } from 'drizzle-orm';
import { withSentry } from '@/server/observability/with-sentry';
import { captureCaught } from '@/server/observability/capture.server';
import { invalidateCatalog } from '@/server/cache/invalidate.js';
import { type DrizzleClient } from '../db/client.js';
import type { MultidealEnv } from '../env.js';
import { dealTranslations } from '../db/schema.js';
import { expireDeals } from '../db/queries/cron-boundary.js';

export interface CronEnv {
  DATABASE_URL: string;
  VAPID_PUBLIC_KEY?: string;
  VAPID_PRIVATE_KEY?: string;
  VAPID_SUBJECT?: string;
  /** Required for monthly settlement email. */
  RESEND_API_KEY?: string;
  RESEND_MARKETING_FROM_EMAIL?: string;
  BREVO_API_KEY?: string;
  /** Brevo contact list ID for audience sync (integer as string). */
  BREVO_LIST_ID?: string;
  /** Club settlement commission rate (default "0.10"). */
  CLUB_SETTLEMENT_RATE?: string;
  /** PII encryption key (used for vendor email decryption). */
  PII_KEY: string;
  /** Payment provider selector — read by getPaymentProvider(). */
  PAYMENT_PROVIDER: 'stripe' | 'mock';
  /** QR secret for purchase QR code generation. */
  QR_SECRET?: string;
  /** R2 bucket binding - used for image retrieval during LLM moderation. */
  R2_BUCKET?: R2Bucket;
  /** Gemini / Google AI API key - used by LLM job processor. */
  GOOGLE_API_KEY?: string;
  // --- Stripe fields (required by getPaymentProvider when PAYMENT_PROVIDER='stripe') ---
  /** Stripe secret key. Set via `wrangler secret put STRIPE_SECRET_KEY`. */
  STRIPE_SECRET_KEY?: string;
  /** Stripe API version pin. Optional — falls back to SDK default. */
  STRIPE_API_VERSION?: string;
  /** Stripe publishable key. */
  STRIPE_PUBLISHABLE_KEY?: string;
  /** Stripe webhook signing secret. */
  STRIPE_WEBHOOK_SECRET?: string;
  /** AES-256-GCM key for encrypting vendor Stripe keys at rest. */
  STRIPE_VENDOR_KEY_ENCRYPTION_KEY?: string;
  /** Platform fee percentage string — used by payment provider. */
  PLATFORM_FEE_PCT?: string;
  /** CF Queue binding for settlement release dispatch (wave 6+). */
  SETTLEMENTS?: {
    sendBatch(
      messages: Array<{
        body: { kind: string; batchKey: string; releaseIds: string[] };
        contentType: 'json';
      }>,
    ): Promise<void>;
  };
  /** Pre-built DB client (neon-http) from DO worker — avoids WebSocket Pool creation in CF scheduled env. */
  db?: DrizzleClient;
  /** Base URL for self-fetch warmup subrequests (e.g. https://dev.multi.deal). */
  PUBLIC_SITE_URL?: string;
  /** Topic DO binding — forwards live CS agent messages on outbox retry path. */
  TOPIC_DO?: MultidealEnv['TOPIC_DO'];
  /** Cloudflare Analytics GraphQL token for queue op-rate monitoring. */
  CF_ANALYTICS_TOKEN?: string;
  /** Cloudflare account ID for Analytics GraphQL queries. */
  CF_ACCOUNT_ID?: string;
  /** Resend from-address for ops alert emails. */
  RESEND_FROM_EMAIL?: string;
  /** Fallback admin notification recipient when system_config list is empty. */
  ADMIN_EMAIL?: string;
  /** Feature flag for deal auto-translation pipeline. String 'true'|'false'. */
  TRANSLATION_ENABLED?: string;
  /** Producer binding for translation job re-enqueue (cron backstop + consumer self-requeue). */
  TRANSLATION_QUEUE?: Queue<{ jobId: string }>;
}

export const runDealExpiry = withSentry(
  async function runDealExpiry(env: CronEnv): Promise<void> {
    const db = env.db ?? createDbService({ DATABASE_URL: env.DATABASE_URL });
    const now = new Date();

    // 1. Expire past-due vouchers via redemption workflow
    const { expirePastDueDeals } = await import('../workflows/redemption.js');
    await expirePastDueDeals({ db });

    // 2. Transition deals with window_end past to EXPIRED state
    const expiredDeals = await expireDeals(db, now);
    if (expiredDeals.length > 0) {
      await invalidateCatalog(db, { scope: 'global' });
    }

    // 3. Send wishlist expiry push alerts (24h before windowEnd)
    try {
      const { getExpiringWishlistDeals } = await import('../db/queries/wishlist.js');
      const expiring = await getExpiringWishlistDeals(db);
      if (expiring.length > 0) {
        const slugMap = new Map<string, string | null>();
        const slugRows = await db
          .select({ dealId: dealTranslations.dealId, slug: dealTranslations.slug })
          .from(dealTranslations)
          .where(
            and(
              inArray(
                dealTranslations.dealId,
                expiring.map((e) => e.dealId),
              ),
              eq(dealTranslations.locale, 'he'),
            ),
          );
        for (const r of slugRows) slugMap.set(r.dealId, r.slug);

        const pushModule = await import('../push/send.js').catch((err) => {
          captureCaught(err, { scope: 'server.cron.deal-expiry', severity: 'info' });
          return null;
        });
        if (pushModule && env.VAPID_PUBLIC_KEY && env.VAPID_PRIVATE_KEY && env.VAPID_SUBJECT) {
          const pushEnv = {
            VAPID_PUBLIC_KEY: env.VAPID_PUBLIC_KEY,
            VAPID_PRIVATE_KEY: env.VAPID_PRIVATE_KEY,
            VAPID_SUBJECT: env.VAPID_SUBJECT,
            DATABASE_URL: env.DATABASE_URL,
          };
          for (const { dealId, userIds } of expiring) {
            for (const userId of userIds) {
              await pushModule
                .sendToUser(
                  db,
                  pushEnv,
                  userId,
                  {
                    title: 'Deal expiring soon!',
                    body: 'A deal in your wishlist expires in less than 24 hours.',
                    url: slugMap.get(dealId) ? `/deals/${slugMap.get(dealId)}` : '/deals',
                    tag: `wishlist-expiry-${dealId}`,
                    data: { eventType: 'WISHLIST_DEAL_EXPIRING', dealId },
                  },
                  'reminder_new_deals',
                )
                .catch((err) => {
                  captureCaught(err, { scope: 'server.cron.deal-expiry', severity: 'info' });
                  // Non-fatal: push failure must not block cron
                });
            }
          }
        }
      }
    } catch (err) {
      captureCaught(err, { scope: 'server.cron.deal-expiry', severity: 'warning' });
      // Push infrastructure may not be available in all environments
    }
  },
  { name: 'cron.deal-expiry', kind: 'cron' },
);
