import { createDbService } from '@/server/services/db.js';
import { eq, inArray } from 'drizzle-orm';
import { captureCaught } from '@/lib/observability';
import { dealCategories, dealTags, vendors } from '../db/schema.js';
import type { CronEnv } from './deal-expiry.js';

const WARM_ROUTES = ['/', '/deals', '/en/deals', '/deals/page-1'];
const MAX_ATTEMPTS = 5;
const RETRY_DELAY_MS = 3000;
const BATCH_SIZE = 8;
/** Cron fires every 30 min — batch index advances once per fire. */
const CRON_INTERVAL_MS = 30 * 60 * 1000;

function sleep(ms: number): Promise<void> {
  return new Promise<void>((resolve) => setTimeout(resolve, ms));
}

function pickBatch(routes: string[]): string[] {
  if (routes.length === 0) return [];
  const batchIndex = Math.floor(Date.now() / CRON_INTERVAL_MS);
  const start = (batchIndex * BATCH_SIZE) % routes.length;
  const batch: string[] = [];
  for (let i = 0; i < BATCH_SIZE; i++) {
    batch.push(routes[(start + i) % routes.length]!);
  }
  return batch;
}

function buildLongTailRoutes(
  categories: { slug: string }[],
  tags: { slug: string }[],
  businesses: { id: string }[],
): string[] {
  const routes: string[] = [];
  for (const { slug } of categories) {
    routes.push(`/category/${slug}`);
  }
  for (const { slug } of tags) {
    routes.push(`/tag/${slug}`);
  }
  for (const { id } of businesses) {
    routes.push(`/business/${id}`);
  }
  return routes;
}

async function warmRoute(base: string, route: string): Promise<void> {
  const url = `${base}${route}`;
  let warmed = false;

  for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
    try {
      // Same URL every attempt — identical cache key. First hit warms the
      // isolate (may 1102); subsequent hits succeed on the warm isolate and
      // prime caches.default so real users get a cache HIT. Plain GET — no
      // force-ssr header (removed; it only skipped caches.default, never forced
      // a fresh render past R2/RenderDO, so it added nothing to the warm).
      const res = await fetch(url);
      if (res.ok) {
        console.warn(
          JSON.stringify({ scope: 'cron.warm-routes', url, attempt, status: res.status }),
        );
        warmed = true;
        break;
      }
      console.warn(
        JSON.stringify({
          scope: 'cron.warm-routes',
          url,
          attempt,
          status: res.status,
          msg: 'retrying',
        }),
      );
    } catch (err) {
      captureCaught(err instanceof Error ? err : new Error(String(err)), {
        scope: 'cron.warm-routes.fetch',
        severity: 'info',
      });
    }
    if (attempt < MAX_ATTEMPTS) await sleep(RETRY_DELAY_MS);
  }

  if (!warmed) {
    console.warn(
      JSON.stringify({ scope: 'cron.warm-routes', url, msg: 'failed after max attempts' }),
    );
  }
}

export async function runWarmRoutes(env: CronEnv): Promise<void> {
  const base = env.PUBLIC_SITE_URL ?? 'https://dev.multi.deal';

  await Promise.allSettled(WARM_ROUTES.map((route) => warmRoute(base, route)));

  if (!env.DATABASE_URL) {
    console.warn('[warm-routes] DATABASE_URL not set — skipping long-tail warmup');
    return;
  }

  let longTailRoutes: string[];
  try {
    const db = env.db ?? createDbService({ DATABASE_URL: env.DATABASE_URL });
    const [categories, tags, businesses] = await Promise.all([
      db
        .select({ slug: dealCategories.slug })
        .from(dealCategories)
        .where(eq(dealCategories.isActive, true)),
      db.select({ slug: dealTags.slug }).from(dealTags).where(eq(dealTags.isActive, true)),
      db
        .select({ id: vendors.id })
        .from(vendors)
        .where(inArray(vendors.accountState, ['ACTIVE', 'VETERAN'])),
    ]);
    longTailRoutes = buildLongTailRoutes(categories, tags, businesses);
  } catch (err) {
    captureCaught(err instanceof Error ? err : new Error(String(err)), {
      scope: 'cron.warm-routes.db',
      severity: 'error',
    });
    console.warn('[warm-routes] long-tail DB query failed — skipping rotating warmup');
    return;
  }

  const batch = pickBatch(longTailRoutes);
  await Promise.allSettled(batch.map((route) => warmRoute(base, route)));
}
