/**
 * GET /api/admin/signals
 *
 * Platform-wide signal overview with stale-while-revalidate caching.
 * Reads from the SAME source the app-side Signals Center uses:
 * generateSignals(brandId) synthesises signals from technical audit,
 * dead pages, conversions, AI visibility, site tag, page lifecycle,
 * rank tracking, competitor data, AND signal_* SiteEvent rows.
 *
 * The previous implementation only queried SiteEvent for actionable
 * event types and reported "0 signals" for any brand whose signals
 * came from synthesised sources (technical issues, rank drops, etc.).
 *
 * Default: 7 days. Admin can widen via ?days=30 (max 90).
 *
 * Cache strategy:
 *   - Fresh (< 5 min old): return cached, no refresh
 *   - Stale (5-60 min old): return cached IMMEDIATELY, refresh in
 *     background so the next request is fresh
 *   - Expired (> 60 min or never): block on the query
 *
 * Superadmin-only.
 */

import { NextResponse } from "next/server";
import { prisma } from "@/lib/prisma";
import { requireSuperadmin, withAdminTiming } from "@/lib/admin-auth";
import { generateSignals } from "@/lib/services/signals";

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

// ── In-memory cache ──────────────────────────────────────────────
interface CachedResult {
  data: Record<string, unknown>;
  timestamp: number;
  days: number;
}
let signalCache: CachedResult | null = null;
const FRESH_TTL = 5 * 60 * 1000;
const STALE_TTL = 60 * 60 * 1000;
let refreshInFlight = false;

async function runQuery(days: number): Promise<Record<string, unknown>> {
  return withAdminTiming("signals.overview", async () => {
    const since = new Date(Date.now() - days * 86_400_000);

    // Iterate every active brand and synthesise signals via the same
    // generateSignals() the app-side Signals Center uses. Per-brand
    // costs are bounded by Promise.allSettled inside generateSignals
    // and an 8s outer race here so one slow brand can't take down the
    // whole dashboard.
    const brands = await prisma.brand.findMany({
      where: { status: { not: "deleted" } },
      select: { id: true, name: true, domain: true },
    });

    type GenResult = { brandId: string; brandName: string; brandDomain: string; signals: Awaited<ReturnType<typeof generateSignals>> };
    const perBrandSettled = await Promise.allSettled(
      brands.map(async (b): Promise<GenResult> => {
        const signals = await Promise.race([
          generateSignals(b.id),
          new Promise<never>((_, rej) => setTimeout(() => rej(new Error("timeout")), 8000)),
        ]).catch(() => []);
        return { brandId: b.id, brandName: b.name, brandDomain: b.domain, signals: signals ?? [] };
      }),
    );

    const perBrand: GenResult[] = [];
    for (const r of perBrandSettled) if (r.status === "fulfilled") perBrand.push(r.value);

    // Filter signals to the requested time window. generateSignals
    // returns a `timestamp` field per signal — when present we honour
    // it; when absent (legacy synthesised signals carry a relative
    // string) we keep the row so the admin doesn't lose signals
    // generated by detectors that don't stamp absolute dates.
    const filterToWindow = (sig: GenResult["signals"][number]) => {
      const t = sig.timestamp ? new Date(sig.timestamp).getTime() : NaN;
      return Number.isFinite(t) ? t >= since.getTime() : true;
    };

    const byTypeMap = new Map<string, number>();
    const byBrandMap = new Map<string, { brandId: string; name: string; domain: string; total: number; critical: number; high: number; medium: number; low: number }>();
    let totalCount = 0;

    for (const b of perBrand) {
      const filtered = b.signals.filter(filterToWindow);
      if (filtered.length === 0) continue;
      const entry = { brandId: b.brandId, name: b.brandName, domain: b.brandDomain, total: 0, critical: 0, high: 0, medium: 0, low: 0 };
      for (const s of filtered) {
        totalCount++;
        entry.total++;
        if (s.priority === "critical") entry.critical++;
        else if (s.priority === "high") entry.high++;
        else if (s.priority === "medium") entry.medium++;
        else if (s.priority === "low") entry.low++;
        // Group by category (issue / opportunity / win / revenue / ai / execution)
        // — that's what the app surface uses as "signal type". The
        // detector-specific source ("rank_tracker", "site-tag-ai", etc.)
        // is finer-grained than admin needs.
        byTypeMap.set(s.category, (byTypeMap.get(s.category) ?? 0) + 1);
      }
      byBrandMap.set(b.brandId, entry);
    }

    const byType = [...byTypeMap.entries()]
      .map(([type, count]) => ({ type, count }))
      .sort((a, b) => b.count - a.count);

    const topBrands = [...byBrandMap.values()]
      .sort((a, b) => b.total - a.total)
      .slice(0, 25)
      .map((b) => ({
        brandId: b.brandId,
        brandName: b.name,
        brandDomain: b.domain,
        count: b.total,
        critical: b.critical,
        high: b.high,
        medium: b.medium,
        low: b.low,
      }));

    // Recent chronological list — flatten signals across brands and
    // sort by timestamp, dropping any without a parseable date.
    const allSignals = perBrand.flatMap((b) =>
      b.signals.filter(filterToWindow).map((s) => ({
        id: s.id,
        title: s.title,
        category: s.category,
        priority: s.priority,
        source: s.source,
        timestamp: s.timestamp,
        brandId: b.brandId,
        brandName: b.brandName,
      })),
    );
    const recent = allSignals
      .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
      .slice(0, 50);

    return {
      windowDays: days,
      totalCount,
      byType,
      topBrands,
      recent,
      brandsScanned: perBrand.length,
    };
  });
}

/** Kick off a cache refresh without blocking the current request.
 *  Guarded by refreshInFlight so multiple concurrent requests
 *  don't all fire the same expensive query. */
function refreshInBackground(days: number) {
  if (refreshInFlight) return;
  refreshInFlight = true;
  runQuery(days)
    .then((data) => {
      signalCache = { data, timestamp: Date.now(), days };
    })
    .catch((err) => {
      console.error("[admin/signals] background refresh failed", err);
    })
    .finally(() => { refreshInFlight = false; });
}

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

  const days = Math.max(1, Math.min(90, Number(new URL(req.url).searchParams.get("days") ?? 7)));
  const now = Date.now();

  // Fresh cache — return immediately, no refresh needed
  if (signalCache && signalCache.days === days && now - signalCache.timestamp < FRESH_TTL) {
    return NextResponse.json({ ...signalCache.data, cached: true, fresh: true }, {
      headers: { "Cache-Control": "private, max-age=60" },
    });
  }

  // Stale cache — return immediately, refresh in the background
  // so the next admin page load sees fresh data.
  if (signalCache && signalCache.days === days && now - signalCache.timestamp < STALE_TTL) {
    const ageSeconds = Math.round((now - signalCache.timestamp) / 1000);
    refreshInBackground(days);
    return NextResponse.json({
      ...signalCache.data,
      cached: true,
      stale: true,
      cacheAge: `${ageSeconds}s`,
    }, {
      headers: { "Cache-Control": "private, max-age=60" },
    });
  }

  // No usable cache — block on the query (first request after a
  // deploy or after > 60 min idle).
  try {
    const result = await runQuery(days);
    signalCache = { data: result as Record<string, unknown>, timestamp: now, days };
    return NextResponse.json(result, {
      headers: { "Cache-Control": "private, max-age=60" },
    });
  } catch (err) {
    const message = err instanceof Error ? err.message : "unknown error";
    console.error("[admin/signals] query failed", { days, message });

    // Any cache at all — even hours old — beats a full error screen
    if (signalCache) {
      const ageMinutes = Math.round((now - signalCache.timestamp) / 60_000);
      return NextResponse.json({
        ...signalCache.data,
        cached: true,
        cacheAge: `${ageMinutes} minutes ago`,
        warning: "Using cached data — live query timed out.",
      });
    }

    return NextResponse.json({
      windowDays: days,
      totalCount: 0,
      byType: [],
      topBrands: [],
      error: "Signal query timed out. The database index may need to be created.",
      partial: true,
    }, { status: 200 });
  }
}
