/**
 * GET /api/cron/data-retention — daily retention purge.
 *
 * For every brand, deletes TrackedEvent / TrackedSession / SiteEvent /
 * SiteConversion rows older than the brand's effective retention
 * period (per-brand override or platform default of 395 days).
 *
 * Deletes in chunks of BATCH_SIZE so a brand with millions of stale
 * rows doesn't blow the function's memory + statement budget.
 * Stops at RUNTIME_CAP_MS so any leftover work resumes on the next
 * cron tick.
 *
 * Logs each per-brand purge to ComplianceEvent under
 * "data_purge_run" so the audit trail records the volume + scope.
 *
 * Protected by CRON_SECRET — same auth pattern as every other cron
 * in src/app/api/cron/*.
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { effectiveRetentionDays } from "@/lib/compliance";

export const dynamic = "force-dynamic";
export const maxDuration = 300;

const CRON_SECRET = process.env.CRON_SECRET ?? "";
const BATCH_SIZE = 5000;
const RUNTIME_CAP_MS = 270_000; // bail at 4.5min, leaving 30s headroom under 5min cap
const MAX_BATCHES_PER_BRAND = 50; // 250K rows per brand per run

function authOk(req: Request): boolean {
  if (!CRON_SECRET) return false;
  const header = req.headers.get("authorization") ?? "";
  if (header === `Bearer ${CRON_SECRET}`) return true;
  return new URL(req.url).searchParams.get("secret") === CRON_SECRET;
}

export async function GET(req: Request) {
  if (!authOk(req)) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  const startedAt = Date.now();

  const brands = await prisma.brand.findMany({
    where: { isDemo: false },
    select: { id: true, dataRetentionDays: true, trackedSite: { select: { id: true } } },
  });

  const summary = {
    brandsScanned: 0,
    brandsPurged: 0,
    rowsDeleted: { events: 0, sessions: 0, siteEvents: 0, conversions: 0 },
    runtimeMs: 0,
    truncated: false,
  };

  for (const brand of brands) {
    if (Date.now() - startedAt > RUNTIME_CAP_MS) {
      summary.truncated = true;
      break;
    }
    summary.brandsScanned++;

    const days = effectiveRetentionDays({ dataRetentionDays: brand.dataRetentionDays });
    const cutoff = new Date(Date.now() - days * 86_400_000);

    const perBrand = { events: 0, sessions: 0, siteEvents: 0, conversions: 0 };

    // Delete-in-batches helper. Returns the number of rows deleted
    // by this batch; the loop continues until either we hit the
    // per-brand max-batches cap, the per-cron runtime cap, or the
    // table runs dry (returned count < BATCH_SIZE).
    const purgeIds = async <T extends { id: string }>(
      find: () => Promise<T[]>,
      del: (ids: string[]) => Promise<{ count: number }>,
    ): Promise<number> => {
      let totalDeleted = 0;
      for (let batch = 0; batch < MAX_BATCHES_PER_BRAND; batch++) {
        if (Date.now() - startedAt > RUNTIME_CAP_MS) break;
        const rows = await find();
        if (rows.length === 0) break;
        const ids = rows.map((r) => r.id);
        const result = await del(ids);
        totalDeleted += result.count;
        if (rows.length < BATCH_SIZE) break;
      }
      return totalDeleted;
    };

    // ── PERMANENT STORAGE — NEVER DELETE ─────────────────────────
    // These tables are the permanent archive that survives beyond
    // Google's retention windows. NEVER add deletion logic for them:
    //   GscPage / GscQuery   — GSC drops data after 16 months
    //   MetricSnapshot       — daily aggregates, permanent
    //   TrackedEvent         — raw Site Tag event archive
    //   TrackedSession       — raw session archive
    //   SiteConversion       — raw conversion archive
    //   AdCampaignData       — ad platform performance archive
    //   AdClickEvent         — ad click archive
    //   OttImpression        — OTT impression archive
    //
    // SiteEvent is the ONLY table purged: its noise types
    // (third_party_impact, slow_resource) are ~99% of its 30M+ rows
    // and have no analytical value after 90 days. Signal types
    // (rage_click, js_error, etc.) are retained permanently.
    //
    // Previously this cron deleted TrackedEvent, TrackedSession, and
    // SiteConversion. That was removed to preserve the raw archive.
    perBrand.events = 0;    // TrackedEvent: PRESERVED
    perBrand.sessions = 0;  // TrackedSession: PRESERVED
    perBrand.conversions = 0; // SiteConversion: PRESERVED

    // SiteEvent: ONLY purge noise types (third_party_impact +
    // slow_resource) — these are performance-observer telemetry rows
    // that no dashboard reads and no user ever sees. Everything else
    // (rage_click, js_error, outbound_click, signals, etc.) stays.
    const PURGEABLE_SITE_EVENT_TYPES = ["third_party_impact", "slow_resource"];
    perBrand.siteEvents = await purgeIds(
      () => prisma.siteEvent.findMany({
        where: {
          brandId: brand.id,
          eventType: { in: PURGEABLE_SITE_EVENT_TYPES },
          timestamp: { lt: cutoff },
        },
        select: { id: true },
        take: BATCH_SIZE,
      }),
      (ids) => prisma.siteConversion.deleteMany({ where: { id: { in: ids } } }),
    );

    const total = perBrand.events + perBrand.sessions + perBrand.siteEvents + perBrand.conversions;
    if (total > 0) {
      summary.brandsPurged++;
      summary.rowsDeleted.events += perBrand.events;
      summary.rowsDeleted.sessions += perBrand.sessions;
      summary.rowsDeleted.siteEvents += perBrand.siteEvents;
      summary.rowsDeleted.conversions += perBrand.conversions;

      // One ComplianceEvent per brand purged. Skipping zero-row
      // brands keeps the audit trail readable.
      await prisma.complianceEvent.create({
        data: {
          action: "data_purge_run",
          brandId: brand.id,
          details: { ...perBrand, retentionDays: days, cutoff: cutoff.toISOString() } as object,
          requestedBy: "cron",
        },
      }).catch(() => {});
    }
  }

  summary.runtimeMs = Date.now() - startedAt;
  console.info("[cron/data-retention]", summary);
  return NextResponse.json(summary);
}
