import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import {
  getConversionMetrics,
  getSessionCount,
  getPageViewCount,
  getFormMetrics,
  getBookingMetrics,
  getUXSignals,
} from "@/lib/services/platform-metrics";

export const dynamic = "force-dynamic";

const CACHE_HEADERS = {
  "Cache-Control": "private, max-age=30, stale-while-revalidate=120",
};

/**
 * GET /api/site-tag/analytics/ux?brandId&range
 *
 * Powers the UX Insights tab. Pulls three concurrent streams:
 *   1. RealUserMetric — Core Web Vitals (lives in a separate
 *      heatmap module; likely empty until that ships).
 *   2. SiteEvent — behavioral signals (rage/dead click, exit
 *      intent, js_error, outbound). Also from the heatmap module.
 *   3. TrackedEvent + TrackedSession — the day-one Site Tag
 *      surface. THIS is the table with 30k+ rows for active
 *      brands; the new page-engagement / forms / phone /
 *      outbound-domain / booking rollups all come from here so
 *      the tab has something real to render while the heatmap
 *      module is still rolling out.
 *
 * Response adds five new top-level sections + `totals` while
 * preserving the original CWV / UX-signals shape for the existing
 * UI.
 */
export async function GET(req: Request) {
  const t0 = Date.now();
  const user = await getCurrentUser();
  if (!user) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });

  const { searchParams } = new URL(req.url);
  const brandId = searchParams.get("brandId");
  const range = searchParams.get("range") || "30d";

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

  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    include: { organization: { include: { memberships: true } } },
  });
  if (!brand || !brand.organization.memberships.some((m) => m.userId === user.id)) {
    return NextResponse.json({ error: "Access denied" }, { status: 403 });
  }

  const days = range === "1d" ? 1 : range === "7d" ? 7 : range === "28d" ? 28 : range === "90d" ? 90 : 30;
  const since = new Date(Date.now() - days * 86_400_000);

  // TrackedEvent carries trackedSiteId, not brandId — resolve the
  // TrackedSite once up front so the engagement / form / phone /
  // outbound / booking queries below can use the indexed column.
  const trackedSite = await prisma.trackedSite
    .findUnique({ where: { brandId }, select: { id: true } })
    .catch(() => null);
  const trackedSiteId = trackedSite?.id ?? null;

  try {
    // Everything parallelised. Sub-queries that have a shared-utility
    // counterpart in platform-metrics.ts now call it so the numbers
    // match every other surface. The remaining Prisma-direct queries
    // (RUM rows, SiteEvent UX rows, event-type groupBy, top-pages
    // groupBy, top-entry-pages groupBy, top-rage-element rows) stay
    // inline because they return arrays / objects that aren't part of
    // the platform-metrics surface.
    const emptyGroup: Array<{ eventType?: string; pageUrl?: string; _count: number }> = [];
    const siteOpts = trackedSiteId ? { trackedSiteId } : {};
    const [
      rumMetrics,
      uxEvents,
      eventTypeCounts,
      topPageViewsRaw,
      topEntryPagesRaw,
      sessionCount,
      uxSignals,
      rageClickRows,
    ] = await Promise.all([
      prisma.realUserMetric
        .findMany({
          where: { brandId, timestamp: { gte: since } },
          select: { pageUrl: true, lcp: true, cls: true, inp: true, ttfb: true },
          orderBy: { timestamp: "desc" },
          take: 5000,
        })
        .catch(() => []),
      prisma.siteEvent
        .findMany({
          where: {
            brandId,
            eventType: { in: ["rage_click", "dead_click", "exit_intent", "js_error", "outbound_click"] },
            timestamp: { gte: since },
          },
          select: { eventType: true, pageUrl: true, eventData: true },
          orderBy: { timestamp: "desc" },
          take: 2000,
        })
        .catch(() => []),
      trackedSiteId
        ? prisma.trackedEvent
            .groupBy({
              by: ["eventType"],
              where: { trackedSiteId, timestamp: { gte: since } },
              _count: true,
            })
            .catch(() => emptyGroup as unknown as Array<{ eventType: string; _count: number }>)
        : Promise.resolve([] as Array<{ eventType: string; _count: number }>),
      trackedSiteId
        ? prisma.trackedEvent
            .groupBy({
              by: ["pageUrl"],
              where: { trackedSiteId, eventType: "page_view", timestamp: { gte: since } },
              _count: true,
              orderBy: { _count: { pageUrl: "desc" } },
              take: 10,
            })
            .catch(() => emptyGroup as unknown as Array<{ pageUrl: string; _count: number }>)
        : Promise.resolve([] as Array<{ pageUrl: string; _count: number }>),
      trackedSiteId
        ? prisma.trackedSession
            .groupBy({
              by: ["entryPage"],
              where: { trackedSiteId, startedAt: { gte: since } },
              _count: true,
              orderBy: { _count: { entryPage: "desc" } },
              take: 10,
            })
            .catch(() => emptyGroup as unknown as Array<{ entryPage: string; _count: number }>)
        : Promise.resolve([] as Array<{ entryPage: string; _count: number }>),
      // Sessions via shared helper — matches every other surface.
      getSessionCount(brandId, since, siteOpts),
      // Frustration signals via shared helper — one groupBy instead
      // of 5 separate counts.
      getUXSignals(brandId, since),
      // Top rage-clicked elements — findMany + in-memory aggregation.
      prisma.siteEvent
        .findMany({
          where: { brandId, eventType: "rage_click", timestamp: { gte: since } },
          select: { pageUrl: true, eventData: true },
          orderBy: { timestamp: "desc" },
          take: 500,
        })
        .catch(() => [] as Array<{ pageUrl: string; eventData: unknown }>),
    ]);

    // ── Core Web Vitals — P75 with outlier filtering ──────────
    // Google reports CWV as 75th-percentile (p75) values, not means.
    // A single stuck-tab or bot page-load with 300s INP would skew
    // a mean but barely moves the p75. Outlier ceilings match
    // Google's "poor" thresholds × 10 — values above these are
    // measurement artefacts, not real user experience.
    const CWV_CEIL = { lcp: 60_000, cls: 10, inp: 30_000, ttfb: 60_000 };
    const lcpAll: number[] = [];
    const clsAll: number[] = [];
    const inpAll: number[] = [];
    const ttfbAll: number[] = [];
    const pageMap: Record<string, { lcp: number[]; cls: number[]; inp: number[]; count: number }> = {};
    let cwvSamples = 0;
    for (const m of rumMetrics) {
      cwvSamples++;
      if (!pageMap[m.pageUrl]) pageMap[m.pageUrl] = { lcp: [], cls: [], inp: [], count: 0 };
      const pg = pageMap[m.pageUrl];
      pg.count++;
      if (m.lcp != null && m.lcp < CWV_CEIL.lcp)   { lcpAll.push(m.lcp);   pg.lcp.push(m.lcp); }
      if (m.cls != null && m.cls < CWV_CEIL.cls)     { clsAll.push(m.cls);   pg.cls.push(m.cls); }
      if (m.inp != null && m.inp < CWV_CEIL.inp)     { inpAll.push(m.inp);   pg.inp.push(m.inp); }
      if (m.ttfb != null && m.ttfb < CWV_CEIL.ttfb) { ttfbAll.push(m.ttfb); }
    }
    function p75(arr: number[]): number {
      if (arr.length === 0) return 0;
      arr.sort((a, b) => a - b);
      return Math.round(arr[Math.floor(arr.length * 0.75)] * 100) / 100;
    }
    const cwv = {
      lcp: p75(lcpAll),
      cls: p75(clsAll),
      inp: p75(inpAll),
      ttfb: p75(ttfbAll),
      samples: cwvSamples,
    };
    const cwvByPage = Object.entries(pageMap)
      .map(([url, pg]) => ({ url, lcp: p75(pg.lcp), cls: p75(pg.cls), inp: p75(pg.inp), samples: pg.count }))
      .sort((a, b) => b.samples - a.samples)
      .slice(0, 20);

    // ── SiteEvent UX signals (unchanged) ─────────────────────
    type UxRow = { page: string; target: string; count: number };
    const rageClicks: Record<string, UxRow> = {};
    const deadClicks: Record<string, UxRow> = {};
    const jsErrors: Record<string, { message: string; page: string; count: number; firstSeen: Date; lastSeen: Date }> = {};
    const exitIntents: Record<string, { page: string; count: number; totalTime: number; totalScroll: number }> = {};
    const outboundByPage: Record<string, { page: string; target: string; count: number }> = {};

    for (const e of uxEvents) {
      const d = e.eventData as Record<string, unknown>;
      if (e.eventType === "rage_click") {
        const key = `${e.pageUrl}::${d.target || "unknown"}`;
        if (!rageClicks[key]) rageClicks[key] = { page: e.pageUrl, target: (d.target as string) || "unknown", count: 0 };
        rageClicks[key].count++;
      } else if (e.eventType === "dead_click") {
        const key = `${e.pageUrl}::${d.target || "unknown"}`;
        if (!deadClicks[key]) deadClicks[key] = { page: e.pageUrl, target: (d.target as string) || "unknown", count: 0 };
        deadClicks[key].count++;
      } else if (e.eventType === "js_error") {
        const msg = (d.message as string) || "Unknown error";
        const key = `${msg}::${e.pageUrl}`;
        if (!jsErrors[key]) jsErrors[key] = { message: msg, page: e.pageUrl, count: 0, firstSeen: new Date(), lastSeen: new Date() };
        jsErrors[key].count++;
        jsErrors[key].lastSeen = new Date();
      } else if (e.eventType === "exit_intent") {
        if (!exitIntents[e.pageUrl]) exitIntents[e.pageUrl] = { page: e.pageUrl, count: 0, totalTime: 0, totalScroll: 0 };
        exitIntents[e.pageUrl].count++;
        exitIntents[e.pageUrl].totalTime += (d.timeOnPage as number) || 0;
        exitIntents[e.pageUrl].totalScroll += (d.scrollDepth as number) || 0;
      } else if (e.eventType === "outbound_click") {
        const domain =
          (typeof d.targetDomain === "string" && d.targetDomain) ||
          (typeof d.host === "string" && d.host) ||
          "unknown";
        const key = `${e.pageUrl}::${domain}`;
        if (!outboundByPage[key]) outboundByPage[key] = { page: e.pageUrl, target: domain, count: 0 };
        outboundByPage[key].count++;
      }
    }

    // ── TrackedEvent rollups ────────────────────────────────
    const eventCounts: Record<string, number> = {};
    let totalEvents = 0;
    for (const row of eventTypeCounts) {
      const et = (row as { eventType: string })?.eventType ?? "";
      const n = typeof row?._count === "number" ? row._count : 0;
      if (et) eventCounts[et] = n;
      totalEvents += n;
    }
    const totalPageViews = eventCounts["page_view"] ?? 0;

    const topPages = topPageViewsRaw.map((r) => ({
      url: (r as { pageUrl: string }).pageUrl,
      views: typeof r._count === "number" ? r._count : 0,
    }));
    const topEntryPages = topEntryPagesRaw.map((r) => ({
      url: (r as { entryPage: string }).entryPage,
      sessions: typeof r._count === "number" ? r._count : 0,
    }));

    // Form starts stay TrackedEvent-sourced (intent signal, no
    // SiteConversion row). Everything else reads from the shared
    // conversion metrics helper so these numbers match the Overview
    // tab's Hard/Soft conversion breakdown exactly.
    const formStarts = eventCounts["form_start"] ?? 0;
    const convMetrics = await getConversionMetrics(brandId, since, {
      sessionsTrackedSiteId: trackedSiteId ?? undefined,
    });
    const formSubmits = convMetrics.byType.form_submit ?? 0;
    const formCompletionRate = formStarts > 0 ? Math.round((formSubmits / formStarts) * 1000) / 10 : 0;

    const phoneClicks = convMetrics.byType.click_to_call ?? 0;
    const smsClicks = convMetrics.byType.sms_click ?? 0;

    // Booking opens/steps are intent signals (TrackedEvent-sourced),
    // submit + confirmed come from the deduped SiteConversion counts
    // so they match the Overview's Conversion Breakdown.
    const booking = {
      widgetOpened: eventCounts["booking_widget_opened"] ?? 0,
      step: eventCounts["booking_step"] ?? 0,
      submit: convMetrics.byType.booking_submit ?? 0,
      confirmed: convMetrics.byType.booking_confirmed ?? 0,
    };

    const pageEngagement = {
      totalPageViews,
      totalSessions: sessionCount,
      avgPagesPerSession: sessionCount > 0 ? Math.round((totalPageViews / sessionCount) * 10) / 10 : 0,
      topPages,
      topEntryPages,
    };

    // ── UX frustration signals ──────────────────────────────
    // Bucket the raw rage-click rows by element_label (falling
    // back to element_tag + element_id when the label is empty so
    // icon-only buttons still group sensibly). Per-page context is
    // forwarded so the UI can show "Submit (on /contact — 42 clicks)".
    const rageBuckets: Record<string, { label: string; page: string; count: number }> = {};
    for (const row of rageClickRows) {
      const d = (row.eventData || {}) as Record<string, unknown>;
      const label =
        (typeof d.element_label === "string" && d.element_label) ||
        (typeof d.element_tag === "string" && d.element_tag) ||
        "unknown";
      const key = label + "::" + row.pageUrl;
      if (!rageBuckets[key]) rageBuckets[key] = { label, page: row.pageUrl, count: 0 };
      rageBuckets[key].count++;
    }
    const topRageElements = Object.values(rageBuckets)
      .sort((a, b) => b.count - a.count)
      .slice(0, 5);

    const frustration = {
      rageClicks: uxSignals.rageClicks,
      deadClicks: uxSignals.deadClicks,
      formAbandoned: uxSignals.formAbandoned,
      scrollBounces: uxSignals.scrollBounces,
      errorsShown: uxSignals.errorsShown,
      topRageElements,
    };

    const elapsed = Date.now() - t0;
    console.info("[site-tag-analytics:ux] query completed", {
      elapsed,
      rumRows: rumMetrics.length,
      uxRows: uxEvents.length,
      trackedEvents: totalEvents,
      sessions: sessionCount,
      range,
      brandId,
    });

    return NextResponse.json(
      {
        cwv,
        cwvByPage,
        rageClicks: Object.values(rageClicks).sort((a, b) => b.count - a.count).slice(0, 20),
        deadClicks: Object.values(deadClicks).sort((a, b) => b.count - a.count).slice(0, 20),
        jsErrors: Object.values(jsErrors).sort((a, b) => b.count - a.count).slice(0, 20),
        exitIntents: Object.values(exitIntents)
          .map((e) => ({
            ...e,
            avgTime: e.count > 0 ? Math.round(e.totalTime / e.count) : 0,
            avgScroll: e.count > 0 ? Math.round(e.totalScroll / e.count) : 0,
          }))
          .sort((a, b) => b.count - a.count)
          .slice(0, 20),
        outboundClicks: Object.values(outboundByPage).sort((a, b) => b.count - a.count).slice(0, 20),

        // New TrackedEvent-derived sections.
        pageEngagement,
        forms: {
          starts: formStarts,
          submits: formSubmits,
          completionRate: formCompletionRate,
        },
        phone: {
          totalClicks: phoneClicks,
          smsClicks,
        },
        booking,
        // UX frustration signals (rage / dead / abandonment / bounce
        // / visible error) — counts + top rage-clicked elements.
        frustration,
        totals: {
          totalEvents,
          totalSessions: sessionCount,
          rumSamples: cwvSamples,
          signalEvents: uxEvents.length,
          conversionRate: convMetrics.rate,
          totalConversions: convMetrics.total,
        },
      },
      { headers: CACHE_HEADERS },
    );
  } catch (err) {
    const elapsed = Date.now() - t0;
    console.error("[site-tag-analytics:ux] failed", {
      elapsed,
      range,
      brandId,
      err: err instanceof Error ? err.message : "unknown",
    });
    return NextResponse.json({
      cwv: { lcp: 0, cls: 0, inp: 0, ttfb: 0, samples: 0 },
      cwvByPage: [],
      rageClicks: [],
      deadClicks: [],
      jsErrors: [],
      exitIntents: [],
      outboundClicks: [],
      pageEngagement: { totalPageViews: 0, totalSessions: 0, avgPagesPerSession: 0, topPages: [], topEntryPages: [] },
      forms: { starts: 0, submits: 0, completionRate: 0 },
      phone: { totalClicks: 0, smsClicks: 0 },
      booking: { widgetOpened: 0, step: 0, submit: 0, confirmed: 0 },
      frustration: { rageClicks: 0, deadClicks: 0, formAbandoned: 0, scrollBounces: 0, errorsShown: 0, topRageElements: [] },
      totals: { totalEvents: 0, totalSessions: 0, rumSamples: 0, signalEvents: 0 },
    });
  }
}
