/**
 * POST /api/site-tag/event
 *
 * Generic UX / intelligence signal ingest: rage_click, dead_click,
 * hesitation, exit_intent, js_error, slow_resource, third_party_impact,
 * tab_visibility, outbound_click, search_query, copy_event, download_click.
 */

import { NextResponse } from "next/server";
import { getCorsHeaders, resolveSiteFromTag } from "@/lib/services/page-lifecycle";
import { prisma } from "@/lib/prisma";

export const dynamic = "force-dynamic";

const ALLOWED_TYPES = new Set([
  // Core UX signals
  "rage_click",
  "dead_click",
  "hesitation",
  "exit_intent",
  "js_error",
  "slow_resource",
  "third_party_impact",
  "tab_visibility",
  "outbound_click",
  "search_query",
  "copy_event",
  "download_click",
  "resource_download",
  "seo_change",
  // Added 2026-04: element visibility, video, chat, session attribution
  "element_visible",
  "video_engagement",
  "chat_interaction",
  "session_attribution",
  // Form completion tracking — full fill stats (separate from form_submit)
  "form_filled",
  // HTTP error + broken link + redirect detection (client-side)
  "http_error",
  "soft_404",
  "broken_link",
  "redirect_detected",
  "resource_error",
  "resource_errors", // batched version — array of {type, url} in eventData.resources
  // Core Web Vitals + performance monitoring (added 2026-04).
  "slow_interaction", // Event Timing API — any interaction whose
                      // duration exceeds 200ms. Carries elementLabel,
                      // delay, interactionType (click / keypress / tap).
  // UX frustration signals (added 2026-04). rage_click + dead_click
  // are already accepted above via their legacy entries; the three
  // below are the new complements.
  "error_shown",      // MutationObserver-detected error element
                      // appearing on the page (.error / .alert-
                      // danger / [role='alert'] / etc.). Carries
                      // error_text + nearest_form_id.
  "form_abandoned",   // Form was started (focusin) but page unloads
                      // without a form_submit. Carries form_id,
                      // fields_filled_count, total_fields, time_spent_ms,
                      // last_field_focused.
  "scroll_bounce",    // Very low engagement on unload: scroll < 15%
                      // AND time < 10s AND no clicks. Carries
                      // max_scroll_depth + time_on_page_ms.
  // Accessibility + navigation patterns (added 2026-04).
  "keyboard_navigation", // 3+ Tab-key presses within a page —
                         // indicates keyboard-only / screen-reader
                         // usage. Carries tab_count.
  "navigation_pattern",  // Browser back / forward / pogo-stick.
                         // Carries pattern (back_button | pogo_stick)
                         // + from_url / to_url.
  "page_not_found",      // Page detected as a 404 by title /
                         // body-text heuristic. Carries
                         // requested_url + referrer — catches
                         // broken incoming links from real
                         // visitors, not just crawlers.
  "accessibility_context", // Once per session: navigator.language,
                           // prefers-reduced-motion,
                           // prefers-color-scheme, screen width,
                           // devicePixelRatio. Drives the
                           // accessibility-demand report.
  // Temporary telemetry probe -- captures DOM/URL deltas when no framework
  // success detector fires; gated to PROBE_BRAND_ALLOWLIST below.
  "form_success_probe",
]);

// Temporary telemetry probe: only store form_success_probe for these brands.
// Add or remove brandIds here; empty array = probe disabled everywhere.
const PROBE_BRAND_ALLOWLIST = new Set(["cmo1ylx3t03j1fyu82rvopqkr"]);

export async function OPTIONS(req: Request) {
  return new NextResponse(null, { status: 204, headers: getCorsHeaders(req) });
}

interface EventBody {
  siteId?: string;
  tagId?: string;
  visitorId?: string | null;
  sessionId?: string | null;
  pageUrl?: string;
  eventType?: string;   // single-event (legacy) format
  eventData?: unknown;
  events?: unknown;     // batched format: [{ eventType, eventData }, ...]
}

export async function POST(req: Request) {
  const cors = getCorsHeaders(req);
  let body: EventBody;
  try {
    body = (await req.json()) as EventBody;
  } catch {
    return NextResponse.json({ ok: false, error: "Invalid JSON" }, { status: 400, headers: cors });
  }

  const siteId = body.siteId || body.tagId;

  // Cheap pre-reject: drop missing or malformed siteIds before any DB work.
  // Format rule: every valid siteId starts with "ms_". Anything else was
  // never registered and will never be found -- skip resolveSiteFromTag entirely.
  if (!siteId || typeof siteId !== "string" || !siteId.startsWith("ms_")) {
    if (Math.random() < 0.01) {
      console.warn("SITE_TAG_DROP", JSON.stringify({
        reason: !siteId ? "missing_site_id" : "malformed_site_id",
        siteId: (siteId ?? "").slice(0, 24),
        origin: req.headers.get("origin") ?? null,
        ua: (req.headers.get("user-agent") ?? "").slice(0, 80),
      }));
    }
    return new NextResponse(null, { status: 204, headers: cors });
  }

  const resolved = await resolveSiteFromTag(siteId, body.pageUrl || "");
  if (!resolved.ok) {
    // 1% sample so we can diagnose drop reasons without log-drain cost.
    // Captures only reject-path data -- zero overhead on the success path.
    if (Math.random() < 0.01) {
      console.warn("SITE_TAG_DROP", JSON.stringify({
        reason: resolved.reason,
        siteId: (siteId ?? "").slice(0, 24),
        origin: req.headers.get("origin") ?? null,
        failedHostname: resolved.failedHostname ?? null,
        ua: (req.headers.get("user-agent") ?? "").slice(0, 80),
      }));
    }
    // Return 200 instead of 403/400 to prevent console flood in
    // preview mode. The event is simply dropped.
    return NextResponse.json({ ok: false, dropped: true }, { status: 200, headers: cors });
  }

  if (!body.pageUrl) {
    return NextResponse.json({ ok: false, error: "pageUrl required" }, { status: 400, headers: cors });
  }

  // Build write list -- accept batch or legacy single-event format.
  // Unknown types are silently dropped from batches; unknown type on a
  // single-event request returns 400 so legacy callers still get feedback.
  type EventRow = { eventType: string; eventData: object };
  const toWrite: EventRow[] = [];

  if (Array.isArray(body.events) && body.events.length > 0) {
    for (const e of (body.events as Array<unknown>).slice(0, 200)) {
      if (!e || typeof (e as Record<string, unknown>).eventType !== "string") continue;
      const et = (e as Record<string, unknown>).eventType as string;
      if (!ALLOWED_TYPES.has(et)) continue;
      toWrite.push({ eventType: et, eventData: ((e as Record<string, unknown>).eventData as object) ?? {} });
    }
  } else if (body.eventType) {
    if (!ALLOWED_TYPES.has(body.eventType)) {
      return NextResponse.json({ ok: false, error: `Unknown eventType: ${body.eventType}` }, { status: 400, headers: cors });
    }
    toWrite.push({ eventType: body.eventType, eventData: (body.eventData as object) ?? {} });
  }

  if (!toWrite.length) {
    return NextResponse.json({ ok: false, error: "No valid events" }, { status: 400, headers: cors });
  }

  // Drop probe events for brands not in the allowlist (silently — 200 so client doesn't retry).
  const filteredToWrite = toWrite.filter((e) =>
    e.eventType !== "form_success_probe" || PROBE_BRAND_ALLOWLIST.has(resolved.site.brandId)
  );
  if (!filteredToWrite.length) {
    return NextResponse.json({ ok: true }, { headers: cors });
  }

  const pageUrl = String(body.pageUrl).slice(0, 2048);
  const visitorId = body.visitorId ?? null;
  const sessionId = body.sessionId ?? null;

  try {
    await prisma.siteEvent.createMany({
      data: filteredToWrite.map((e) => ({
        brandId: resolved.site.brandId,
        visitorId,
        sessionId,
        pageUrl,
        eventType: e.eventType,
        eventData: e.eventData,
      })),
    });
  } catch (err) {
    console.error("[site-tag/event] write failed", err);
    return NextResponse.json({ ok: false, error: "Write failed" }, { status: 500, headers: cors });
  }

  return NextResponse.json({ ok: true }, { headers: cors });
}
