/**
 * GET /api/admin/analytics/engagement
 *
 * Platform engagement rollup. Sources every metric from
 * PlatformEvent + User — no third-party dependencies. Designed to
 * stay useful at low scale (3 users) by computing infrastructure
 * that lights up automatically as the platform grows.
 *
 * Returns:
 *   activeUsers:        { dau, wau, mau, stickiness }
 *   featureAdoption:    [{ action, category, users, sessions, lastUsed }]
 *   cohortRetention:    [{ cohortLabel, cohortSize, weeks: [{ index, retained, pct }] }]
 *   activityDistribution: { power, regular, occasional, dormant }
 *
 * Superadmin-only. maxDuration 30s.
 */

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;
const WEEK_MS = 7 * DAY_MS;

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

  try {
    return NextResponse.json(await withAdminTiming("analytics.engagement", async () => {
      const now = Date.now();
      const oneDayAgo = new Date(now - DAY_MS);
      const sevenDaysAgo = new Date(now - 7 * 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);

      // ── Active users ─────────────────────────────────────────
      // Each window: distinct userIds on PlatformEvent. Three
      // separate groupBy queries hit the [userId, timestamp]
      // index directly — sub-second on any plausible volume.
      const [dauRows, wauRows, mauRows, allUsers] = await Promise.all([
        prisma.platformEvent.groupBy({
          by: ["userId"],
          where: { timestamp: { gte: oneDayAgo }, userId: { not: null } },
        }).catch(() => [] as Array<{ userId: string | null }>),
        prisma.platformEvent.groupBy({
          by: ["userId"],
          where: { timestamp: { gte: sevenDaysAgo }, userId: { not: null } },
        }).catch(() => [] as Array<{ userId: string | null }>),
        prisma.platformEvent.groupBy({
          by: ["userId"],
          where: { timestamp: { gte: thirtyDaysAgo }, userId: { not: null } },
        }).catch(() => [] as Array<{ userId: string | null }>),
        prisma.user.count().catch(() => 0),
      ]);
      const dau = dauRows.length;
      const wau = wauRows.length;
      const mau = mauRows.length;
      const stickiness = mau > 0 ? Math.round((dau / mau) * 1000) / 10 : 0;

      // ── Feature adoption ────────────────────────────────────
      // For each action key: distinct users + total sessions
      // (groupBy proxy — count of events per action) + most recent
      // use. Returns top 25 by user count so the table fits.
      let featureAdoption: Array<{ action: string; category: string; users: number; sessions: number; lastUsed: string | null; pctOfUsers: number }> = [];
      try {
        const [actionUserCounts, actionEventCounts, actionLastSeen] = await Promise.all([
          prisma.platformEvent.groupBy({
            by: ["action", "category"],
            where: { timestamp: { gte: thirtyDaysAgo }, userId: { not: null } },
            _count: { userId: true },
          }),
          prisma.platformEvent.groupBy({
            by: ["action"],
            where: { timestamp: { gte: thirtyDaysAgo } },
            _count: true,
          }),
          prisma.platformEvent.groupBy({
            by: ["action"],
            where: { timestamp: { gte: thirtyDaysAgo } },
            _max: { timestamp: true },
          }),
        ]);
        // Re-derive distinct users per action — Prisma groupBy
        // _count.userId is the count of non-null user rows, not
        // distinct. Walk the userIds × actions through a smaller
        // findMany to compute distinct.
        const distinctUsersByAction = new Map<string, Set<string>>();
        const actionEvents = await prisma.platformEvent.findMany({
          where: { timestamp: { gte: thirtyDaysAgo }, userId: { not: null } },
          select: { action: true, userId: true },
          take: 50_000,
        });
        for (const e of actionEvents) {
          if (!e.userId) continue;
          const s = distinctUsersByAction.get(e.action) ?? new Set<string>();
          s.add(e.userId);
          distinctUsersByAction.set(e.action, s);
        }
        const eventCountByAction = new Map(actionEventCounts.map((r) => [r.action, r._count]));
        const lastSeenByAction = new Map(actionLastSeen.map((r) => [r.action, r._max.timestamp]));
        featureAdoption = actionUserCounts
          .map((r) => {
            const distinctUsers = distinctUsersByAction.get(r.action)?.size ?? 0;
            return {
              action: r.action,
              category: r.category,
              users: distinctUsers,
              sessions: eventCountByAction.get(r.action) ?? 0,
              lastUsed: lastSeenByAction.get(r.action)?.toISOString() ?? null,
              pctOfUsers: allUsers > 0 ? Math.round((distinctUsers / allUsers) * 1000) / 10 : 0,
            };
          })
          .filter((r) => r.users > 0)
          .sort((a, b) => b.users - a.users || b.sessions - a.sessions)
          .slice(0, 25);
      } catch (err) {
        console.warn("[engagement] feature adoption failed", err);
      }

      // ── Cohort retention ────────────────────────────────────
      // Group every user by signup WEEK (Sunday-aligned). For each
      // cohort row, compute % retained in week 0/1/2/3/4/5/6/7/8.
      // "Retained" = user fired ≥1 PlatformEvent in that ISO week.
      // Sets up the infra now even at 3 users so it lights up at
      // scale without a refactor.
      let cohortRetention: Array<{ cohortLabel: string; cohortSize: number; weeks: Array<{ index: number; retained: number; pct: number }> }> = [];
      try {
        const users = await prisma.user.findMany({
          where: { createdAt: { gte: new Date(now - 90 * DAY_MS) } },
          select: { id: true, createdAt: true },
          take: 5_000,
        });
        // Bucket users by Sunday-of-week (UTC).
        const cohorts = new Map<string, { weekStart: Date; userIds: Set<string> }>();
        for (const u of users) {
          const ws = startOfWeek(u.createdAt);
          const key = ws.toISOString().slice(0, 10);
          const c = cohorts.get(key) ?? { weekStart: ws, userIds: new Set<string>() };
          c.userIds.add(u.id);
          cohorts.set(key, c);
        }
        // For each cohort, pull every PlatformEvent for those users
        // in the first 9 weeks and bucket by week-offset.
        const cohortKeys = Array.from(cohorts.keys()).sort();
        for (const key of cohortKeys) {
          const c = cohorts.get(key)!;
          const userIds = Array.from(c.userIds);
          const events = await prisma.platformEvent.findMany({
            where: {
              userId: { in: userIds },
              timestamp: { gte: c.weekStart, lt: new Date(c.weekStart.getTime() + 9 * WEEK_MS) },
            },
            select: { userId: true, timestamp: true },
            take: 20_000,
          });
          // weekIndex → set of userIds active in that week.
          const activeByWeek = new Map<number, Set<string>>();
          for (const e of events) {
            if (!e.userId) continue;
            const offset = Math.floor((e.timestamp.getTime() - c.weekStart.getTime()) / WEEK_MS);
            if (offset < 0 || offset > 8) continue;
            const s = activeByWeek.get(offset) ?? new Set<string>();
            s.add(e.userId);
            activeByWeek.set(offset, s);
          }
          const weeks = Array.from({ length: 9 }, (_, i) => {
            const retained = activeByWeek.get(i)?.size ?? 0;
            return {
              index: i,
              retained,
              pct: c.userIds.size > 0 ? Math.round((retained / c.userIds.size) * 1000) / 10 : 0,
            };
          });
          cohortRetention.push({
            cohortLabel: key,
            cohortSize: c.userIds.size,
            weeks,
          });
        }
      } catch (err) {
        console.warn("[engagement] cohort retention failed", err);
      }

      // ── Activity distribution ───────────────────────────────
      // Bucket every user by their most-recent PlatformEvent:
      //   power     — fired an event in the last 24h
      //   regular   — last 7 days
      //   occasional — last 30 days
      //   dormant   — older or never
      let activityDistribution = { power: 0, regular: 0, occasional: 0, dormant: 0 };
      try {
        const allUsersWithLastSeen = await prisma.platformEvent.groupBy({
          by: ["userId"],
          where: { userId: { not: null } },
          _max: { timestamp: true },
        });
        const lastSeenByUser = new Map(
          allUsersWithLastSeen
            .filter((r): r is { userId: string; _max: { timestamp: Date | null } } => !!r.userId)
            .map((r) => [r.userId, r._max.timestamp]),
        );
        const allUsersFull = await prisma.user.findMany({ select: { id: true } });
        for (const u of allUsersFull) {
          const last = lastSeenByUser.get(u.id);
          if (!last) { activityDistribution.dormant++; continue; }
          const ms = now - last.getTime();
          if (ms <= DAY_MS) activityDistribution.power++;
          else if (ms <= 7 * DAY_MS) activityDistribution.regular++;
          else if (ms <= 30 * DAY_MS) activityDistribution.occasional++;
          else activityDistribution.dormant++;
        }
      } catch (err) {
        console.warn("[engagement] activity distribution failed", err);
      }

      // Use the older window markers in the response so the UI can
      // caption the cohort grid + churn-risk references without
      // re-deriving from JS Date math.
      void sixtyDaysAgo; void ninetyDaysAgo;

      return {
        scope: { totalUsers: allUsers, generatedAt: new Date().toISOString() },
        activeUsers: { dau, wau, mau, stickiness },
        featureAdoption,
        cohortRetention,
        activityDistribution,
      };
    }));
  } catch (err) {
    const message = err instanceof Error ? err.message : "unknown error";
    console.error("[admin/analytics/engagement] failed", message);
    return NextResponse.json(
      { error: "Engagement aggregation failed.", detail: message },
      { status: 500 },
    );
  }
}

// Sunday-aligned start-of-week. Matches ISO calendar week
// indexing so the cohort labels are stable across renders.
function startOfWeek(d: Date): Date {
  const r = new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
  r.setUTCDate(r.getUTCDate() - r.getUTCDay());
  return r;
}
