/**
 * GET /api/admin/system-health
 *
 * Heavy system rollup for the Health Monitor page (kept separate
 * from /api/admin/health which is the lightweight "are integrations
 * up" probe used by the dashboard health badge).
 *
 * Returns:
 *   tableCounts        — row counts for every key Prisma model
 *   lastCronRuns       — most-recent PlatformLog timestamp per
 *                        cron source key (best-effort; cron logs
 *                        come from PlatformLog when wired)
 *   recentErrors       — last 5 PlatformLog rows with level=error
 *
 * Superadmin-only.
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSuperadmin, withAdminTiming } from "@/lib/admin-auth";
import { NOISE_SITE_EVENT_TYPES } from "@/lib/services/site-event-noise";

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

export async function GET() {
  const denied = await requireSuperadmin();
  if (denied) return denied;

  return NextResponse.json(await withAdminTiming("system.health", async () => {
    // 13 parallel COUNT queries — each hits a single index, total
    // latency dominated by the slowest. Worst case ~50-100ms on
    // moderate volumes.
    const [
      users, brands, organizations,
      siteEvents, trackedEvents, trackedSessions, siteConversions,
      gscQueries, gscPages,
      technicalAudits, performanceAudits,
      competitorPages, aiInteractions, platformEvents,
    ] = await Promise.all([
      prisma.user.count(),
      prisma.brand.count(),
      prisma.organization.count(),
      // SiteEvent volume is dominated by third_party_impact +
      // slow_resource (99% of rows, ~30M combined on the canary
      // environment). Excluding them keeps this count focused on
      // actual user-behaviour signals — rage clicks, dead clicks,
      // JS errors, outbound clicks — which is what the Health
      // Monitor's "recent events" KPI is actually measuring. The
      // unfiltered count was also a known timeout source on the
      // 30s Vercel function budget.
      prisma.siteEvent.count({
        where: { NOT: { eventType: { in: [...NOISE_SITE_EVENT_TYPES] } } },
      }),
      prisma.trackedEvent.count(),
      prisma.trackedSession.count(),
      // intentionally unfiltered: admin-only endpoint; needs raw or all-time counts
      prisma.siteConversion.count(),
      prisma.gscQuery.count(),
      prisma.gscPage.count(),
      prisma.technicalAudit.count(),
      prisma.performanceAudit.count(),
      prisma.competitorPage.count(),
      prisma.aiInteraction.count(),
      prisma.platformEvent.count(),
    ]);

    // Last cron-run timestamps from PlatformLog. Cron handlers log
    // their start under source = "cron" with the cron name in
    // metadata; we group by message keyword as a fallback because
    // not every cron writes a structured message yet. Surface what
    // we have; missing cron names render "—" in the UI.
    const recentCronLogs = await prisma.platformLog.findMany({
      where: { source: "cron" },
      orderBy: { timestamp: "desc" },
      take: 100,
      select: { source: true, message: true, timestamp: true, statusCode: true },
    }).catch(() => []);

    // Bucket by detected cron name (from message string). Crude but
    // good enough to populate the table; a future cron-instrumentation
    // pass should write a structured cronName field on PlatformLog.
    const lastByCron = new Map<string, { timestamp: Date; statusCode: number | null }>();
    const KNOWN_CRONS = [
      "refresh-tokens", "signal-detection", "signals-site-tag-ai",
      "signals-competitor-regional", "rank-check", "brand-sitemap-crawl",
      "data-retention", "platform-snapshot", "competitor-crawl",
    ];
    for (const log of recentCronLogs) {
      const matched = KNOWN_CRONS.find((c) => log.message.toLowerCase().includes(c));
      if (matched && !lastByCron.has(matched)) {
        lastByCron.set(matched, { timestamp: log.timestamp, statusCode: log.statusCode });
      }
    }

    const recentErrors = await prisma.platformLog.findMany({
      where: { level: "error" },
      orderBy: { timestamp: "desc" },
      take: 5,
      select: { id: true, source: true, message: true, timestamp: true, endpoint: true },
    }).catch(() => []);

    // Approximate DB size — Postgres "select pg_database_size()"
    // would be more accurate but requires a raw query that may not
    // work on every Neon plan. The table-count sum is a reasonable
    // proxy for "how much data are we storing" until we add a
    // dedicated db-size probe.
    const totalRows =
      users + brands + organizations + siteEvents + trackedEvents +
      trackedSessions + siteConversions + gscQueries + gscPages +
      technicalAudits + performanceAudits + competitorPages +
      aiInteractions + platformEvents;

    return {
      tableCounts: {
        User: users,
        Brand: brands,
        Organization: organizations,
        SiteEvent: siteEvents,
        TrackedEvent: trackedEvents,
        TrackedSession: trackedSessions,
        SiteConversion: siteConversions,
        GscQuery: gscQueries,
        GscPage: gscPages,
        TechnicalAudit: technicalAudits,
        PerformanceAudit: performanceAudits,
        CompetitorPage: competitorPages,
        AiInteraction: aiInteractions,
        PlatformEvent: platformEvents,
      },
      totalRows,
      lastCronRuns: KNOWN_CRONS.map((name) => {
        const last = lastByCron.get(name);
        return {
          name,
          lastRunAt: last?.timestamp.toISOString() ?? null,
          lastStatus: last?.statusCode ?? null,
        };
      }),
      recentErrors: recentErrors.map((e) => ({
        ...e,
        timestamp: e.timestamp.toISOString(),
      })),
    };
  }));
}
