/**
 * POST /api/admin/cleanup-conversions
 *
 * One-time cleanup for the form-phone.js over-counting bug (fixed in
 * commit 7e86631). The bug caused fetch/XHR intercepts to fire for
 * every third-party POST on the page, generating ~5x duplicate
 * form_submit rows per real conversion.
 *
 * Two cleanup passes:
 *
 * 1. **Tracking-URL purge**: Delete siteConversion rows whose pageUrl
 *    or formFields contain known analytics/tracking/pixel patterns
 *    (google-analytics, doubleclick, hotjar, etc.). These were never
 *    real conversions — they are fetch/XHR intercepts of third-party
 *    beacons.
 *
 * 2. **Rapid-fire dedup**: Group remaining form_submit rows by
 *    (sessionId, pageUrl). Within each group (ordered by timestamp),
 *    keep the first row and delete subsequent rows that are within 30
 *    seconds of the previous kept row. This preserves legitimate
 *    repeat conversions (e.g., submitting two different forms minutes
 *    apart) while collapsing the duplicate detections (native submit +
 *    fetch + XHR + MutationObserver all firing within <3s).
 *
 * Usage:
 *   POST /api/admin/cleanup-conversions
 *   Body: { "brandId": "cmnf61xrt000613ofqt7h4syf" }
 *   → dry-run (default): logs what would be deleted, deletes nothing
 *
 *   POST /api/admin/cleanup-conversions
 *   Body: { "brandId": "cmnf61xrt000613ofqt7h4syf", "execute": true }
 *   → actually deletes the identified rows
 *
 * Requires admin auth. Idempotent — safe to run multiple times.
 */

import { NextResponse } from "next/server";
import { requireAdmin } from "@/lib/admin-auth";
import { prisma } from "@/lib/prisma";

export const dynamic = "force-dynamic";

/** Same pattern used by the fixed form-phone.js to filter tracking URLs. */
const TRACKING_URL_RE =
  /\/(collect|analytics|pixel|beacon|track|event|log|rum|heatmap|api\/v\d|__)\b|google-analytics|googletagmanager|facebook\.net|doubleclick|hotjar|clarity\.ms|intercom|drift|tawk|sentry|segment\.io|mixpanel|amplitude|\.gif(\?|$|&)|\.png(\?|$|&)/i;

/** Dedup window: rows within this many ms of the previous kept row in
 *  the same (session, page) group are considered duplicates. */
const DEDUP_WINDOW_MS = 60_000;

/** Max form_submit events per session. No real user submits this many
 *  distinct forms in one session — anything beyond is bot or duplicate. */
const MAX_FORM_SUBMITS_PER_SESSION = 3;

interface CleanupResult {
  dryRun: boolean;
  brandId: string;
  before: { total: number; byType: Record<string, number>; byMethod: Record<string, number> };
  trackingPurge: { matched: number; deletedIds: string[] };
  rapidFireDedup: { matched: number; deletedIds: string[] };
  sessionCap: { matched: number; sessionsAffected: number };
  interceptPurge: { matched: number };
  after: { total: number; byType: Record<string, number> };
}

export async function POST(req: Request) {
  const auth = await requireAdmin();
  if (!auth.ok) return NextResponse.json({ error: "Forbidden" }, { status: auth.status });

  let body: { brandId?: string; execute?: boolean };
  try {
    body = await req.json();
  } catch {
    return NextResponse.json({ error: "Invalid JSON" }, { status: 400 });
  }

  const brandId = body.brandId;
  if (!brandId) return NextResponse.json({ error: "brandId required" }, { status: 400 });

  const dryRun = !body.execute;

  // ── Snapshot: before counts ──────────────────────────────────────
  // intentionally unfiltered: admin-only endpoint; needs raw or all-time counts
  const allRows = await prisma.siteConversion.findMany({
    where: { brandId },
    select: { id: true, conversionType: true, detectionMethod: true, pageUrl: true, sessionId: true, timestamp: true, formFields: true },
    orderBy: { timestamp: "asc" },
  });

  const beforeByType: Record<string, number> = {};
  const beforeByMethod: Record<string, number> = {};
  for (const r of allRows) {
    beforeByType[r.conversionType] = (beforeByType[r.conversionType] ?? 0) + 1;
    const method = r.detectionMethod ?? "unknown";
    beforeByMethod[method] = (beforeByMethod[method] ?? 0) + 1;
  }

  // ── Pass 1: Tracking-URL purge ───────────────────────────────────
  // Check pageUrl and formFields for tracking patterns. The fetch/XHR
  // intercept stored the intercepted URL in formFields[0].type.
  const trackingIds: string[] = [];
  for (const r of allRows) {
    if (TRACKING_URL_RE.test(r.pageUrl)) {
      trackingIds.push(r.id);
      continue;
    }
    // formFields may be an array with {name: "_url", type: "<the URL>"}
    // or an object with stats. Check both shapes.
    const ff = r.formFields as unknown;
    if (Array.isArray(ff)) {
      for (const field of ff) {
        const val = (field as Record<string, unknown>)?.type;
        if (typeof val === "string" && TRACKING_URL_RE.test(val)) {
          trackingIds.push(r.id);
          break;
        }
      }
    }
  }

  // ── Pass 2: Rapid-fire dedup (form_submit AND form_filled) ───────
  // Group by (sessionId, pageUrl) — within each group, keep the first
  // row and delete subsequent rows within a 60-second window.
  const pass1Purged = new Set(trackingIds);
  const formRows = allRows.filter(
    (r) => (r.conversionType === "form_submit" || r.conversionType === "form_filled") && !pass1Purged.has(r.id),
  );

  const groups = new Map<string, typeof formRows>();
  for (const r of formRows) {
    const key = `${r.sessionId}||${r.pageUrl}||${r.conversionType}`;
    let g = groups.get(key);
    if (!g) { g = []; groups.set(key, g); }
    g.push(r);
  }

  const rapidFireIds: string[] = [];
  for (const [, rows] of groups) {
    let lastKeptTs = 0;
    for (const r of rows) {
      const ts = r.timestamp.getTime();
      if (lastKeptTs === 0) {
        lastKeptTs = ts;
      } else if (ts - lastKeptTs < DEDUP_WINDOW_MS) {
        rapidFireIds.push(r.id);
      } else {
        lastKeptTs = ts;
      }
    }
  }

  // ── Pass 3: Session cap — max N form_submits per session ──────────
  // No real user submits 4+ forms in a single session. Anything beyond
  // MAX_FORM_SUBMITS_PER_SESSION is bot traffic or duplicate detection.
  const pass12Purged = new Set([...trackingIds, ...rapidFireIds]);
  const sessionCapIds: string[] = [];
  const sessionFormCounts = new Map<string, typeof formRows>();
  for (const r of allRows) {
    if (r.conversionType !== "form_submit" || pass12Purged.has(r.id)) continue;
    let g = sessionFormCounts.get(r.sessionId);
    if (!g) { g = []; sessionFormCounts.set(r.sessionId, g); }
    g.push(r);
  }
  for (const [, rows] of sessionFormCounts) {
    if (rows.length <= MAX_FORM_SUBMITS_PER_SESSION) continue;
    // Keep the first N, delete the rest
    for (let i = MAX_FORM_SUBMITS_PER_SESSION; i < rows.length; i++) {
      sessionCapIds.push(rows[i].id);
    }
  }

  // ── Pass 4: Intercept purge — delete ALL fetch_intercept and ──────
  // xhr_intercept records. These detection methods are fundamentally
  // broken: they fire for every same-origin POST (HubSpot chat, cookie
  // consent, CMS AJAX, lazy-loading) not just form submissions. The
  // client-side intercepts have been removed in form-phone.js, and
  // all historical records from these methods are false positives.
  const interceptIds: string[] = [];
  const pass123Purged = new Set([...trackingIds, ...rapidFireIds, ...sessionCapIds]);
  for (const r of allRows) {
    if (pass123Purged.has(r.id)) continue;
    if (r.detectionMethod === "fetch_intercept" || r.detectionMethod === "xhr_intercept") {
      interceptIds.push(r.id);
    }
  }

  // ── Execute or dry-run ───────────────────────────────────────────
  const allDeleteIds = [...new Set([...trackingIds, ...rapidFireIds, ...sessionCapIds, ...interceptIds])];

  if (!dryRun && allDeleteIds.length > 0) {
    // Delete in batches of 500 to avoid exceeding query limits.
    for (let i = 0; i < allDeleteIds.length; i += 500) {
      const batch = allDeleteIds.slice(i, i + 500);
      await prisma.siteConversion.deleteMany({
        where: { id: { in: batch } },
      });
    }
  }

  // ── Snapshot: after counts ───────────────────────────────────────
  const deletedSet = new Set(allDeleteIds);
  const afterByType: Record<string, number> = {};
  let afterTotal = 0;
  for (const r of allRows) {
    if (!deletedSet.has(r.id)) {
      afterByType[r.conversionType] = (afterByType[r.conversionType] ?? 0) + 1;
      afterTotal++;
    }
  }

  const sessionsWithCap = new Set(sessionCapIds.map((id) => {
    const row = allRows.find((r) => r.id === id);
    return row?.sessionId ?? "";
  }));

  // ── Diagnostics: top sessions + pages (for debugging) ─────────────
  const diagSessionCounts = new Map<string, number>();
  const diagPageCounts = new Map<string, number>();
  for (const r of allRows) {
    if (deletedSet.has(r.id)) continue;
    diagSessionCounts.set(r.sessionId, (diagSessionCounts.get(r.sessionId) ?? 0) + 1);
    const key = `${r.conversionType}::${r.pageUrl}`;
    diagPageCounts.set(key, (diagPageCounts.get(key) ?? 0) + 1);
  }
  const topSessionsAfter = [...diagSessionCounts.entries()]
    .sort(([, a], [, b]) => b - a)
    .slice(0, 10)
    .map(([sessionId, count]) => ({ sessionId: sessionId.slice(0, 20), count }));
  const topPagesAfter = [...diagPageCounts.entries()]
    .sort(([, a], [, b]) => b - a)
    .slice(0, 10)
    .map(([key, count]) => {
      const [type, ...urlParts] = key.split("::");
      return { type, pageUrl: urlParts.join("::").slice(0, 100), count };
    });

  const result: CleanupResult = {
    dryRun,
    brandId,
    before: { total: allRows.length, byType: beforeByType, byMethod: beforeByMethod },
    trackingPurge: { matched: trackingIds.length, deletedIds: dryRun ? trackingIds.slice(0, 20) : [] },
    rapidFireDedup: { matched: rapidFireIds.length, deletedIds: dryRun ? rapidFireIds.slice(0, 20) : [] },
    sessionCap: { matched: sessionCapIds.length, sessionsAffected: sessionsWithCap.size },
    interceptPurge: { matched: interceptIds.length },
    after: { total: afterTotal, byType: afterByType },
  };

  console.log("[cleanup-conversions]", JSON.stringify(result, null, 2));
  return NextResponse.json({ ...result, diagnostics: { topSessionsAfter, topPagesAfter } });
}
