/**
 * GET /api/admin/analytics/growth
 *
 * Growth + retention rollup. Sources:
 *   monthly  — User / Brand / TrackedEvent / TrackedSession new
 *               counts grouped by signup month (last 12 months).
 *   churn    — every user bucketed by days since last PlatformEvent.
 *   funnel   — six activation milestones from signup → first
 *               report-generated event, with % completion at each.
 *   mrr      — pre-Stripe projection: brands × plan-price band.
 *
 * Superadmin-only. maxDuration 30s. Per-section try/catch.
 */

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

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

const DAY_MS = 86_400_000;

// Pre-Stripe MRR estimator. Plans documented in pricing page; if a
// brand isn't on a paid plan yet we assume Professional ($249) for
// the upper-bound projection. The "if all paying" headline uses
// this assumption explicitly.
const PLAN_PRICE_USD = {
  starter:      149,
  professional: 249,
  growth:       499,
  enterprise: 2_499,
};

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

  try {
    return NextResponse.json(await withAdminTiming("analytics.growth", async () => {
      const now = Date.now();
      const twelveMonthsAgo = new Date(now - 365 * DAY_MS);
      const thirtyDaysAgo = new Date(now - 30 * DAY_MS);
      const sixtyDaysAgo  = new Date(now - 60 * DAY_MS);
      const ninetyDaysAgo = new Date(now - 90 * DAY_MS);

      // ── Monthly growth metrics ─────────────────────────────
      // Pull createdAt timestamps for User + Brand and bucket by
      // month client-side. TrackedEvent + TrackedSession use the
      // same approach over the timestamp/startedAt columns.
      let monthly: { users: Array<{ month: string; count: number }>; brands: Array<{ month: string; count: number }>; events: Array<{ month: string; count: number }>; sessions: Array<{ month: string; count: number }> } = {
        users: [], brands: [], events: [], sessions: [],
      };
      try {
        const [userRows, brandRows, eventRows, sessionRows] = await Promise.allSettled([
          prisma.user.findMany({
            where: { createdAt: { gte: twelveMonthsAgo } },
            select: { createdAt: true },
            take: 50_000,
          }),
          prisma.brand.findMany({
            where: { createdAt: { gte: twelveMonthsAgo } },
            select: { createdAt: true },
            take: 50_000,
          }),
          prisma.trackedEvent.findMany({
            where: { timestamp: { gte: twelveMonthsAgo } },
            select: { timestamp: true },
            take: 100_000,
          }),
          prisma.trackedSession.findMany({
            where: { startedAt: { gte: twelveMonthsAgo } },
            select: { startedAt: true },
            take: 100_000,
          }),
        ]);
        const get = <T>(r: PromiseSettledResult<T>, fb: T): T => r.status === "fulfilled" ? r.value : fb;
        monthly = {
          users:    bucketByMonth(get(userRows,    [] as Array<{ createdAt: Date }>).map((r) => r.createdAt)),
          brands:   bucketByMonth(get(brandRows,   [] as Array<{ createdAt: Date }>).map((r) => r.createdAt)),
          events:   bucketByMonth(get(eventRows,   [] as Array<{ timestamp: Date }>).map((r) => r.timestamp)),
          sessions: bucketByMonth(get(sessionRows, [] as Array<{ startedAt: Date }>).map((r) => r.startedAt)),
        };
      } catch (err) {
        console.warn("[growth] monthly aggregation failed", err);
      }

      // ── Churn analysis ─────────────────────────────────────
      // Bucket every user by their most recent PlatformEvent age:
      //   active    — fired an event in the last 30 days
      //   atRisk    — 30-60 days
      //   churned   — 60-90 days
      //   lost      — 90+ days OR never fired one
      let churn: { active: number; atRisk: number; churned: number; lost: number; atRiskUsers: Array<{ id: string; email: string | null; lastSeen: string | null }> } = {
        active: 0, atRisk: 0, churned: 0, lost: 0, atRiskUsers: [],
      };
      try {
        const [users, lastSeenRows] = await Promise.all([
          prisma.user.findMany({ select: { id: true, email: true } }),
          prisma.platformEvent.groupBy({
            by: ["userId"],
            where: { userId: { not: null } },
            _max: { timestamp: true },
          }),
        ]);
        const lastSeen = new Map<string, Date | null>(
          lastSeenRows
            .filter((r): r is { userId: string; _max: { timestamp: Date | null } } => !!r.userId)
            .map((r) => [r.userId, r._max.timestamp]),
        );
        for (const u of users) {
          const ls = lastSeen.get(u.id) ?? null;
          if (!ls) { churn.lost++; continue; }
          const ms = now - ls.getTime();
          if (ms <= 30 * DAY_MS)      churn.active++;
          else if (ms <= 60 * DAY_MS) {
            churn.atRisk++;
            if (churn.atRiskUsers.length < 25) {
              churn.atRiskUsers.push({ id: u.id, email: u.email, lastSeen: ls.toISOString() });
            }
          }
          else if (ms <= 90 * DAY_MS) churn.churned++;
          else                         churn.lost++;
        }
        churn.atRiskUsers.sort((a, b) => (a.lastSeen ?? "").localeCompare(b.lastSeen ?? ""));
      } catch (err) {
        console.warn("[growth] churn analysis failed", err);
      }
      // Keep the older window markers wired even when not directly
      // used in case a future revision needs them.
      void sixtyDaysAgo; void ninetyDaysAgo; void thirtyDaysAgo;

      // ── Activation funnel ──────────────────────────────────
      // Six milestones every brand should hit, ordered by typical
      // sequence. Each step counts the brands that have completed
      // that step OR ANY downstream step (so completion is
      // monotonic — drop-off only happens at gates).
      let funnel: Array<{ step: string; label: string; count: number; pct: number }> = [];
      try {
        const totalBrands = await prisma.brand.count();
        // Step counts — each independently catches.
        const [
          brandsAdded, sitesInstalled, ga4Connected, gscConnected,
          aiUsed, reportGenerated,
        ] = await Promise.all([
          // Step 1: brand added — every Brand row qualifies.
          Promise.resolve(totalBrands),
          // Step 2: Site Tag installed — TrackedSite exists for the brand.
          prisma.trackedSite.count(),
          // Step 3: GA4 connected.
          prisma.brandIntegration.count({
            where: { integrationId: "ga4", connected: true },
          }),
          // Step 4: GSC connected.
          prisma.brandIntegration.count({
            where: { integrationId: "gsc", connected: true },
          }),
          // Step 5: any AI feature used — at least one AiUsageLog
          // row scoped to the brand.
          prisma.aiUsageLog.findMany({
            where: { brandId: { not: null } },
            select: { brandId: true },
            distinct: ["brandId"],
            take: 5_000,
          }).then((r) => r.length),
          // Step 6: report-generation event — first PlatformEvent
          // with action containing "report" or "export".
          prisma.platformEvent.findMany({
            where: {
              brandId: { not: null },
              action: { in: ["report_generated", "report_exported", "email_report_sent"] },
            },
            select: { brandId: true },
            distinct: ["brandId"],
            take: 5_000,
          }).then((r) => r.length),
        ]);
        const steps = [
          { step: "signed_up",        label: "Signed up",            count: totalBrands },
          { step: "brand_added",      label: "Added first brand",    count: brandsAdded },
          { step: "site_tag",         label: "Installed Site Tag",   count: sitesInstalled },
          { step: "ga4_connected",    label: "Connected GA4",        count: ga4Connected },
          { step: "gsc_connected",    label: "Connected GSC",        count: gscConnected },
          { step: "ai_used",          label: "Used AI feature",      count: aiUsed },
          { step: "report_generated", label: "Generated report",     count: reportGenerated },
        ];
        const denom = totalBrands || 1;
        funnel = steps.map((s) => ({
          ...s,
          pct: Math.round((s.count / denom) * 1000) / 10,
        }));
      } catch (err) {
        console.warn("[growth] activation funnel failed", err);
      }

      // ── MRR projection ─────────────────────────────────────
      // Pre-Stripe — every brand counted at the Professional band
      // ($249/mo) for the upper-bound headline. Operators who know
      // the actual plan distribution can override the assumption
      // by reading the per-brand list returned here.
      let mrr: { ifAllPaying: number; brandCount: number; assumption: string; growthPerMonth: number } = {
        ifAllPaying: 0, brandCount: 0, assumption: "", growthPerMonth: 0,
      };
      try {
        const brandCount = await prisma.brand.count();
        const last30dBrands = await prisma.brand.count({
          where: { createdAt: { gte: thirtyDaysAgo } },
        });
        mrr = {
          ifAllPaying: brandCount * PLAN_PRICE_USD.professional,
          brandCount,
          assumption: `Assumes every brand on Professional plan ($${PLAN_PRICE_USD.professional}/mo). Range: $${PLAN_PRICE_USD.starter}-${PLAN_PRICE_USD.enterprise}.`,
          growthPerMonth: last30dBrands * PLAN_PRICE_USD.professional,
        };
      } catch (err) {
        console.warn("[growth] mrr projection failed", err);
      }

      return {
        scope: { generatedAt: new Date().toISOString() },
        monthly,
        churn,
        funnel,
        mrr,
      };
    }));
  } catch (err) {
    const message = err instanceof Error ? err.message : "unknown error";
    console.error("[admin/analytics/growth] failed", message);
    return NextResponse.json(
      { error: "Growth aggregation failed.", detail: message },
      { status: 500 },
    );
  }
}

// Bucket a list of timestamps into per-calendar-month counts for
// the last 12 months. Returns one entry per month even when the
// month is empty so the chart has gap-free spacing.
function bucketByMonth(timestamps: Date[]): Array<{ month: string; count: number }> {
  const map = new Map<string, number>();
  for (const t of timestamps) {
    const key = `${t.getUTCFullYear()}-${String(t.getUTCMonth() + 1).padStart(2, "0")}`;
    map.set(key, (map.get(key) ?? 0) + 1);
  }
  // Fill the trailing 12 months in chronological order.
  const out: Array<{ month: string; count: number }> = [];
  const now = new Date();
  for (let i = 11; i >= 0; i--) {
    const d = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth() - i, 1));
    const key = `${d.getUTCFullYear()}-${String(d.getUTCMonth() + 1).padStart(2, "0")}`;
    out.push({ month: key, count: map.get(key) ?? 0 });
  }
  return out;
}
