/**
 * GET /api/admin/overview — platform summary for the admin dashboard.
 */
import { NextResponse } from "next/server";
import { requireAdmin } from "@/lib/admin-auth";
import { NOISE_SITE_EVENT_TYPES } from "@/lib/services/site-event-noise";
import { prisma } from "@/lib/prisma";

export const dynamic = "force-dynamic";

// ─── In-memory response cache (60s TTL) ──────────────────────────────────────
// Survives across requests within the same serverless instance. Cold starts
// miss the cache but revalidate it; subsequent requests within the window
// return immediately without touching the DB.

interface CacheEntry { data: unknown; expiresAt: number }
const _cache = new Map<string, CacheEntry>();
const CACHE_TTL_MS = 60_000;

function getCached(key: string): unknown | null {
  const entry = _cache.get(key);
  if (!entry || Date.now() > entry.expiresAt) { _cache.delete(key); return null; }
  return entry.data;
}
function setCached(key: string, data: unknown): void {
  _cache.set(key, { data, expiresAt: Date.now() + CACHE_TTL_MS });
}

// Coverage categories — which event types we consider a "category
// active" signal. Kept in lock-step with the Site Tag Analytics
// overview tab's EVENT_CATEGORIES table + the Session Explorer
// timeline palette so the same surfaces read consistently across
// every place they appear. Presence of ANY listed type flips the
// dot green.
//
// Events land in three different tables depending on their route:
//   • TrackedEvent  — /api/collect (conversions, navigation, most
//                     t.js-emitted events)
//   • SiteEvent     — /api/site-tag/event (UX signals, JS errors,
//                     resource errors, slow interactions, etc.)
//   • RealUserMetric — /api/site-tag/rum (Core Web Vitals — no
//                      eventType column; presence ⇒ synthetic
//                      "web_vitals" counter)
// The handler below queries all three and merges into a single
// eventType-keyed count map before classifying.
const COVERAGE_CATEGORIES: Array<{ key: string; label: string; types: string[] }> = [
  { key: "core",        label: "Core",        types: ["page_view", "session_start", "outbound_click", "page_lifecycle"] },
  { key: "forms",       label: "Forms",       types: ["form_start", "form_submit", "form_abandoned", "form_intent", "form_filled"] },
  { key: "phone",       label: "Phone",       types: ["click_to_call", "sms_click", "sms_submit", "phone_number_visible", "call_tracking_detected"] },
  { key: "booking",     label: "Booking",     types: ["booking_opened", "booking_widget_opened", "booking_submit", "booking_confirmed", "booking_step", "appointment_intent", "appointment_modify_intent"] },
  { key: "video",       label: "Video",       types: ["video_play", "video_progress", "video_complete", "video_engagement"] },
  { key: "ecommerce",   label: "E-commerce",  types: ["product_view", "product_click", "add_to_cart", "cart_view", "cart_update", "cart_abandoned", "checkout_started", "checkout_step", "purchase", "purchase_completed", "payment_method_selected"] },
  { key: "ux_signals",  label: "UX Signals",  types: ["rage_click", "dead_click", "form_abandoned", "scroll_bounce", "error_shown"] },
  { key: "performance", label: "Performance", types: ["web_vitals", "slow_interaction", "js_error", "resource_error", "resource_errors", "page_not_found"] },
  { key: "social",      label: "Social",      types: ["social_share", "content_copied", "review_widget_visible", "review_widget_clicked", "social_proof_visible", "social_proof_attention"] },
  { key: "content",     label: "Content",     types: ["content_engagement", "text_selected", "site_search", "file_download", "pdf_opened", "pdf_time_spent", "tab_pattern", "page_printed"] },
  { key: "competitor",  label: "Competitor",  types: ["competitor_visit", "competitor_referral", "competitive_journey"] },
  { key: "chat",        label: "Chat",        types: ["chat_opened", "chat_widget_detected", "chat_message_sent", "chat_interaction"] },
  { key: "scheduling",  label: "Scheduling",  types: ["calendar_add", "appointment_modify_intent", "availability_check", "waitlist_signup"] },
];

export async function GET() {
  const t0 = Date.now();
  const auth = await requireAdmin();
  if (!auth.ok) return NextResponse.json({ error: "Forbidden" }, { status: auth.status });

  // Serve from cache if warm (same serverless instance, within TTL)
  const cached = getCached("overview");
  if (cached) {
    console.log(`[overview] cache hit — returning in ${Date.now() - t0}ms`);
    return NextResponse.json(cached, { headers: { "Cache-Control": "private, max-age=120, stale-while-revalidate=300" } });
  }

  const now = new Date();
  const d7 = new Date(now.getTime() - 7 * 86400000);
  const d30 = new Date(now.getTime() - 30 * 86400000);
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());

  const tQueries = Date.now();
  const [
    totalOrgs, totalBrands, totalUsers, activeBrands,
    gscCount, ga4Count, gbpCount, siteTags,
    aiToday, aiWeek, aiMonth, costMonth,
    // 7-day distinct-event-type rollups across the three
    // ingest tables. Each query independently .catch'd to [] / 0
    // so one failing aggregate zeros that slice of coverage
    // without blanking the whole overview.
    trackedEventTypeRows,
    siteEventTypeRows,
    rumCount,
  ] = await Promise.all([
    prisma.organization.count(),
    prisma.brand.count(),
    prisma.user.count(),
    prisma.brand.count({ where: { updatedAt: { gte: d7 } } }),
    prisma.brandIntegration.count({ where: { integrationId: "gsc", connected: true } }),
    prisma.brandIntegration.count({ where: { integrationId: "ga4", connected: true } }),
    prisma.gbpLocation.count({ where: { isActive: true } }),
    prisma.trackedSite.count({ where: { status: "active" } }),
    prisma.aiUsageLog.count({ where: { timestamp: { gte: today } } }),
    prisma.aiUsageLog.count({ where: { timestamp: { gte: d7 } } }),
    prisma.aiUsageLog.count({ where: { timestamp: { gte: d30 } } }),
    prisma.aiUsageLog.aggregate({ where: { timestamp: { gte: d30 } }, _sum: { estimatedCost: true } }),
    prisma.trackedEvent
      .groupBy({
        by: ["eventType"],
        where: { timestamp: { gte: d7 } },
        _count: true,
      })
      .catch(() => [] as Array<{ eventType: string; _count: number }>),
    // SiteEvent groupBy MUST exclude the noise types
    // (third_party_impact + slow_resource = 99% of the 30M-row
    // table). Without the filter this cross-brand groupBy does a
    // full sequential scan and 504s on the 30s Vercel budget.
    prisma.siteEvent
      .groupBy({
        by: ["eventType"],
        where: {
          timestamp: { gte: d7 },
          NOT: { eventType: { in: [...NOISE_SITE_EVENT_TYPES] } },
        },
        _count: true,
      })
      .catch(() => [] as Array<{ eventType: string; _count: number }>),
    prisma.realUserMetric
      .count({ where: { timestamp: { gte: d7 } } })
      .catch(() => 0),
  ]);
  console.log(`[overview] parallel queries took ${Date.now() - tQueries}ms`);

  // Merge both event-type streams into one type → count map. A
  // handful of types exist in both tables historically (outbound_
  // click was re-routed mid-2025); summing is the right rollup for
  // a "was this category active anywhere" question.
  const countsByType: Record<string, number> = {};
  for (const row of trackedEventTypeRows) {
    if (!row.eventType) continue;
    countsByType[row.eventType] = (countsByType[row.eventType] ?? 0) + (typeof row._count === "number" ? row._count : 0);
  }
  for (const row of siteEventTypeRows) {
    if (!row.eventType) continue;
    countsByType[row.eventType] = (countsByType[row.eventType] ?? 0) + (typeof row._count === "number" ? row._count : 0);
  }
  // RealUserMetric has no eventType column — the whole table IS
  // web_vitals. Synthesise the bucket so the Performance category
  // picks it up with the same per-type lookup as every other
  // category.
  if (rumCount > 0) {
    countsByType["web_vitals"] = (countsByType["web_vitals"] ?? 0) + rumCount;
  }
  // Debug breadcrumb — grep-friendly on Vercel when a "category
  // looks inactive" report comes in. Cap the array slice at 80
  // entries so a brand with a hundred custom types doesn't turn
  // the log line into a megabyte.
  console.info(
    "[tracking-coverage] Active event types:",
    JSON.stringify(Object.entries(countsByType).sort((a, b) => b[1] - a[1]).slice(0, 80)),
  );

  const categories = COVERAGE_CATEGORIES.map((cat) => {
    let total = 0;
    const activeTypes: string[] = [];
    for (const t of cat.types) {
      const n = countsByType[t] ?? 0;
      if (n > 0) {
        total += n;
        activeTypes.push(t);
      }
    }
    return {
      key: cat.key,
      label: cat.label,
      total,
      active: activeTypes.length > 0,
      activeTypes,
    };
  });
  const activeCount = categories.filter((c) => c.active).length;

  const responseBody = {
    totalOrgs, totalBrands, totalUsers, activeBrands,
    integrations: { gsc: gscCount, ga4: ga4Count, gbpLocations: gbpCount },
    siteTags, ai: { today: aiToday, week: aiWeek, month: aiMonth },
    estimatedCostMonth: Math.round((costMonth._sum.estimatedCost ?? 0) * 100) / 100,
    trackingCoverage: {
      windowDays: 7,
      totalDistinctTypes: Object.keys(countsByType).length,
      activeCategories: activeCount,
      totalCategories: COVERAGE_CATEGORIES.length,
      categories,
    },
  };

  setCached("overview", responseBody);
  console.log(`[overview] total handler time ${Date.now() - t0}ms`);

  return NextResponse.json(responseBody, {
    headers: {
      "Cache-Control": "private, max-age=120, stale-while-revalidate=300",
    },
  });
}
