import { timingSafeEqual } from "node:crypto";

import { and, eq, isNull, lte, or } from "drizzle-orm";
import { NextResponse } from "next/server";

import { checkQuota } from "@/lib/billing/quotas";
import { db } from "@/lib/db";
import { sites } from "@/lib/db/schema";
import { env } from "@/lib/env";
import { launchSitespeedTest } from "@/lib/sitespeed-launch";

const BEARER_PREFIX = "Bearer ";

const CADENCE_INTERVAL_MS: Record<string, number> = {
  daily: 24 * 60 * 60 * 1000,
  weekly: 7 * 24 * 60 * 60 * 1000,
};

function validateCronAuth(
  authHeader: string | null,
): { ok: true } | { ok: false; status: 401 | 503 } {
  if (!env.CRON_SECRET) return { ok: false, status: 503 };
  if (!authHeader || !authHeader.startsWith(BEARER_PREFIX)) return { ok: false, status: 401 };
  const provided = authHeader.slice(BEARER_PREFIX.length);
  const encoder = new TextEncoder();
  const a = encoder.encode(provided);
  const b = encoder.encode(env.CRON_SECRET);
  if (a.length !== b.length) return { ok: false, status: 401 };
  if (!timingSafeEqual(a, b)) return { ok: false, status: 401 };
  return { ok: true };
}

export async function POST(req: Request) {
  const auth = validateCronAuth(req.headers.get("authorization"));
  if (!auth.ok) {
    const message = auth.status === 503 ? "CRON_SECRET not configured" : "Unauthorized";
    return NextResponse.json({ error: message }, { status: auth.status });
  }

  const now = new Date();
  const nowIso = now.toISOString();

  const dueSites = await db
    .select()
    .from(sites)
    .where(
      and(
        or(eq(sites.auditCadence, "daily"), eq(sites.auditCadence, "weekly")),
        or(isNull(sites.nextAuditAt), lte(sites.nextAuditAt, nowIso)),
      ),
    );

  const results: Array<{ siteId: string; ok: boolean; testId?: string; error?: string }> = [];

  for (const site of dueSites) {
    const interval = CADENCE_INTERVAL_MS[site.auditCadence];
    if (!interval) continue;

    // Per-org monthly test cap. Boundary note: when used === limit - 1, we
    // still allow this run (it lands exactly at the limit); the next tick
    // will see used === limit and be skipped below.
    const quota = await checkQuota(site.organizationId, "tests");
    if (!quota.ok) {
      results.push({
        siteId: site.id,
        ok: false,
        error: `Quota exceeded: ${quota.used}/${quota.limit} tests this month for org ${site.organizationId}`,
      });
      continue;
    }

    const launched = await launchSitespeedTest({
      url: site.url,
      hostname: site.hostname,
      device: "desktop",
      siteId: site.id,
      organizationId: site.organizationId,
      triggeredByUserId: null,
    });

    if (launched.ok) {
      const next = new Date(now.getTime() + interval).toISOString();
      await db
        .update(sites)
        .set({ lastAuditAt: nowIso, nextAuditAt: next })
        .where(eq(sites.id, site.id));
      results.push({ siteId: site.id, ok: true, testId: launched.id });
    } else {
      // Don't advance nextAuditAt on failure — retry on the next tick.
      results.push({ siteId: site.id, ok: false, error: launched.error });
    }
  }

  return NextResponse.json({
    runAt: nowIso,
    candidates: dueSites.length,
    triggered: results.filter((r) => r.ok).length,
    failed: results.filter((r) => !r.ok).length,
    results,
  });
}
