/**
 * GET /api/admin/sessions/[sessionId]?brandId=X
 *
 * Full journey reconstruction for a single session. Returns the
 * minimal shape the detail page needs:
 *
 *   {
 *     session:     { id, brandId, startedAt, duration, entryPage,
 *                    pageCount, eventCount, conversionCount,
 *                    plus extra metadata fields kept for richer UI },
 *     timeline:    [{ timestamp, type, pageUrl, metadata, ...extras }],
 *     conversions: [{ type, timestamp, pageUrl, ...extras }],
 *     aggregates:  { totalPages, totalEvents, totalConversions,
 *                    plus per-type breakdowns + repeat-click tally }
 *   }
 *
 * sessionId path param accepts either the internal cuid
 * (TrackedSession.id) or the raw client string (TrackedSession.sessionId)
 * — the route tries both. Each downstream query is wrapped in its own
 * try/catch so one failing source produces an empty array on that
 * field rather than a 500. 404 + { error } when the session genuinely
 * isn't found for the brand.
 *
 * Superadmin-gated. maxDuration 30s.
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSuperadmin, withAdminTiming } from "@/lib/admin-auth";
import { canonicalConversionType } from "@/lib/conversion-taxonomy";

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

export async function GET(
  req: Request,
  { params }: { params: Promise<{ sessionId: string }> },
) {
  const denied = await requireSuperadmin();
  if (denied) return denied;

  const { sessionId: pathId } = await params;
  const { searchParams } = new URL(req.url);
  const brandId = searchParams.get("brandId");
  if (!brandId) {
    return NextResponse.json({ error: "brandId required" }, { status: 400 });
  }

  try {
    const result = await withAdminTiming("admin.sessions.detail", async () => {
      // ── Resolve site for this brand ──
      let site: { id: string } | null = null;
      try {
        site = await prisma.trackedSite.findUnique({
          where: { brandId }, select: { id: true },
        });
      } catch (err) {
        console.warn("[admin/sessions/detail] site lookup failed", err);
      }
      if (!site) {
        return { status: 404 as const, body: { error: "No TrackedSite for this brand." } };
      }

      // ── Resolve session — try internal cuid first, then raw string ──
      let session: Awaited<ReturnType<typeof prisma.trackedSession.findFirst>> = null;
      try {
        session = await prisma.trackedSession.findFirst({
          where: { id: pathId, trackedSiteId: site.id },
        });
        if (!session) {
          session = await prisma.trackedSession.findFirst({
            where: { sessionId: pathId, trackedSiteId: site.id },
          });
        }
      } catch (err) {
        console.warn("[admin/sessions/detail] session lookup failed", err);
      }
      if (!session) {
        return { status: 404 as const, body: { error: "Session not found" } };
      }
      // Re-bind for narrowing — TS can't see the not-null guard
      // through the let-reassignment pattern above.
      const sess = session;

      // ── Pull events + conversions for this session ──
      // Each in its own try/catch so one failing source returns []
      // rather than 500-ing the whole route.
      type EventRow = {
        id: string; eventType: string; pageUrl: string;
        referrer: string | null; timestamp: Date;
        metadata: unknown;
      };
      type ConversionRow = {
        id: string; conversionType: string; pageUrl: string;
        timestamp: Date; formId: string | null;
        formProvider: string | null; detectionMethod: string | null;
        phoneNumber: string | null; formFields: unknown;
      };
      let events: EventRow[] = [];
      let conversions: ConversionRow[] = [];
      try {
        events = await prisma.trackedEvent.findMany({
          where: { sessionId: sess.id },
          orderBy: { timestamp: "asc" },
          select: {
            id: true, eventType: true, pageUrl: true, referrer: true,
            timestamp: true, metadata: true,
          },
          take: 1000,
        });
      } catch (err) {
        console.warn("[admin/sessions/detail] events query failed", err);
      }
      try {
        // intentionally unfiltered: admin-only endpoint; needs raw or all-time counts
        conversions = await prisma.siteConversion.findMany({
          where: { sessionId: sess.sessionId, brandId },
          orderBy: { timestamp: "asc" },
          select: {
            id: true, conversionType: true, pageUrl: true, timestamp: true,
            formId: true, formProvider: true, detectionMethod: true,
            phoneNumber: true, formFields: true,
          },
          take: 500,
        });
      } catch (err) {
        console.warn("[admin/sessions/detail] conversions query failed", err);
      }

      // ── Build unified timeline ──
      type TimelineEntry = {
        kind: "event" | "conversion";
        id: string;
        type: string;
        canonicalType: string | null;
        timestamp: string;
        pageUrl: string;
        metadata: Record<string, unknown>;
        formFields?: Record<string, unknown> | null;
        isConversion: boolean;
      };
      const timeline: TimelineEntry[] = [];
      for (const e of events) {
        timeline.push({
          kind: "event",
          id: e.id,
          type: e.eventType,
          canonicalType: canonicalConversionType(e.eventType),
          timestamp: e.timestamp.toISOString(),
          pageUrl: e.pageUrl,
          metadata: (e.metadata ?? {}) as Record<string, unknown>,
          isConversion: false,
        });
      }
      for (const c of conversions) {
        timeline.push({
          kind: "conversion",
          id: c.id,
          type: c.conversionType,
          canonicalType: canonicalConversionType(c.conversionType),
          timestamp: c.timestamp.toISOString(),
          pageUrl: c.pageUrl,
          metadata: {
            formId: c.formId,
            formProvider: c.formProvider,
            detectionMethod: c.detectionMethod,
            phoneNumber: c.phoneNumber,
          },
          formFields: (c.formFields ?? null) as Record<string, unknown> | null,
          isConversion: true,
        });
      }
      timeline.sort((a, b) => a.timestamp.localeCompare(b.timestamp));

      // ── Slim conversions list for the UI ──
      const conversionList = conversions.map((c) => ({
        id: c.id,
        type: c.conversionType,
        canonicalType: canonicalConversionType(c.conversionType),
        timestamp: c.timestamp.toISOString(),
        pageUrl: c.pageUrl,
        source: c.formProvider ?? c.detectionMethod ?? null,
      }));

      // ── Aggregates ──
      const eventCountByType: Record<string, number> = {};
      for (const e of events) {
        eventCountByType[e.eventType] = (eventCountByType[e.eventType] ?? 0) + 1;
      }
      const conversionCountByCanonical: Record<string, number> = {};
      const interactionCountByCanonical: Record<string, number> = {};
      for (const c of conversions) {
        const canon = canonicalConversionType(c.conversionType) ?? c.conversionType;
        conversionCountByCanonical[canon] = (conversionCountByCanonical[canon] ?? 0) + 1;
        const ff = (c.formFields ?? {}) as Record<string, unknown>;
        const ic = typeof ff._interaction_count === "number" ? ff._interaction_count : 1;
        interactionCountByCanonical[canon] = (interactionCountByCanonical[canon] ?? 0) + ic;
      }
      // Repeat phone-click tally — same number clicked > 1× in a session
      // is a UX signal worth surfacing.
      const phoneClickCounts: Record<string, number> = {};
      for (const c of conversions) {
        if (canonicalConversionType(c.conversionType) !== "phone_call") continue;
        const phone = c.phoneNumber || "(unknown)";
        phoneClickCounts[phone] = (phoneClickCounts[phone] ?? 0) + 1;
      }
      for (const e of events) {
        if (e.eventType !== "click_to_call") continue;
        const meta = (e.metadata ?? {}) as Record<string, unknown>;
        const phone =
          (typeof meta.phone_normalized === "string" && meta.phone_normalized) ||
          (typeof meta.phone_number === "string" && meta.phone_number) ||
          "(unknown)";
        phoneClickCounts[phone] = (phoneClickCounts[phone] ?? 0) + 1;
      }

      const durationMs = Math.max(0, sess.lastSeenAt.getTime() - sess.startedAt.getTime());
      const totalEvents = events.length;
      const totalConversions = conversions.length;

      return {
        status: 200 as const,
        body: {
          session: {
            // Spec'd fields
            id: sess.sessionId,
            brandId,
            startedAt: sess.startedAt.toISOString(),
            duration: durationMs,
            entryPage: sess.entryPage,
            pageCount: sess.pageCount,
            eventCount: totalEvents,
            conversionCount: totalConversions,
            // Extras kept for the richer detail UI
            sessionId: sess.sessionId,
            internalId: sess.id,
            lastSeenAt: sess.lastSeenAt.toISOString(),
            durationMs,
            referrer: sess.referrer,
            source: sess.utmSource ?? (sess.referrer ? "referral" : "direct"),
            medium: sess.utmMedium,
            campaign: sess.utmCampaign,
            country: sess.country,
            device: sess.device,
            userAgent: sess.userAgent,
            // New device/geo fields — superadmin-only surface.
            // ipAddress returned only from the detail endpoint (gated
            // behind requireSuperadmin above) and never from the list.
            browser: sess.browser,
            os: sess.os,
            city: sess.city,
            region: sess.region,
            ipAddress: sess.ipAddress,
          },
          timeline,
          conversions: conversionList,
          aggregates: {
            // Spec'd fields
            totalPages: sess.pageCount,
            totalEvents,
            totalConversions,
            // Extras
            eventCountByType,
            conversionCountByCanonical,
            interactionCountByCanonical,
            phoneClickCounts,
            uniquePhoneNumbers: Object.keys(phoneClickCounts).length,
          },
        },
      };
    });

    return NextResponse.json(result.body, { status: result.status });
  } catch (err) {
    const message = err instanceof Error ? err.message : "unknown error";
    console.error("[admin/sessions/detail] failed", message);
    return NextResponse.json(
      { error: "Session detail aggregation failed.", detail: message },
      { status: 500 },
    );
  }
}
