/**
 * GET /api/site-tag/analytics/pulse?brandId=xxx
 * ----------------------------------------------
 * Lightweight heartbeat for the Site Tag Analytics LIVE-mode 5s poll.
 *
 * Hot path (Redis cache hit): zero DB connections acquired.
 *
 * Cold path (cache miss): one point-query for auth + site info, then one
 * point-query against SiteTagDailyRollup for eventsToday. No live
 * TrackedEvent aggregate. Both wrapped in a 3s Promise.race so a slow
 * connection-acquire returns 503 fast instead of hanging to maxDuration.
 *
 * lastEventAt is kept fresh by a Redis key written fire-and-forget by
 * the event write path (site-tag.ts) on each incoming event. Pulse
 * prefers that value over the DB column because it is updated per-event
 * rather than per hourly-cron.
 *
 * eventsToday comes from SiteTagDailyRollup (hourly cron), so it may be
 * up to ~1h stale within the current day. Acceptable for a heartbeat
 * whose only job is to tell the client "something changed".
 *
 * Response: { installed: boolean, lastEventAt: string|null, eventsToday: number }
 *
 * Security: brand-membership verified on every cold path. Cache key is
 * scoped to (userId, brandId) so a cached response is never served cross-user.
 */

import { NextResponse } from "next/server";
import { getCurrentUser } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
import { getCached, setCached } from "@/lib/cache/redis-cache";
import { pulseResponseKey, pulseLastEventKey } from "@/lib/cache/pulse-keys";

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

const LIVE_CACHE_HEADERS = {
  "Cache-Control": "private, no-store, no-cache, max-age=0, must-revalidate",
  "Pragma": "no-cache",
  "Expires": "0",
};

const CACHE_TTL_S = 60;
const DB_TIMEOUT_MS = 3_000;
const REDIS_TIMEOUT_MS = 200;

interface PulsePayload {
  installed: boolean;
  lastEventAt: string | null;
  eventsToday: number;
}

type DbResult =
  | { kind: "timeout" }
  | { kind: "not_found" }
  | { kind: "unauthorized" }
  | { kind: "no_site" }
  | {
      kind: "ok";
      lastEventAt: Date | null;
      eventsToday: number;
      redisLast: string | null;
    };

/** Read from Redis with a hard 200ms timeout so Redis never stalls the tick. */
async function readCache(userId: string, brandId: string): Promise<PulsePayload | null> {
  return Promise.race([
    getCached<PulsePayload>(pulseResponseKey(userId, brandId)),
    new Promise<null>((resolve) => setTimeout(() => resolve(null), REDIS_TIMEOUT_MS)),
  ]);
}

async function runDbQueries(
  userId: string,
  brandId: string,
  todayUTC: Date,
): Promise<DbResult> {
  // Step 1: auth + site info -- one indexed point query
  const brand = await prisma.brand.findUnique({
    where: { id: brandId },
    select: {
      organization: { select: { memberships: { select: { userId: true } } } },
      trackedSite: { select: { id: true, lastEventAt: true } },
    },
  });

  if (!brand) return { kind: "not_found" };
  if (!brand.organization.memberships.some((m) => m.userId === userId)) {
    return { kind: "unauthorized" };
  }

  const site = brand.trackedSite;
  if (!site) return { kind: "no_site" };

  // Step 2: rollup read + Redis lastEventAt concurrently.
  // siteTagDailyRollup is an indexed point query, NOT an aggregate.
  // getCached does not use a Postgres connection.
  const [rollup, redisLast] = await Promise.all([
    prisma.siteTagDailyRollup.findUnique({
      where: { brandId_date: { brandId, date: todayUTC } },
      select: { eventCountsByType: true, pageViews: true },
    }),
    getCached<string>(pulseLastEventKey(brandId)),
  ]);

  let eventsToday = 0;
  if (rollup) {
    const cts = rollup.eventCountsByType as Record<string, number> | null;
    if (cts && typeof cts === "object") {
      eventsToday = Object.values(cts).reduce(
        (s, v) => s + (typeof v === "number" ? v : 0),
        0,
      );
    } else {
      eventsToday = rollup.pageViews;
    }
  }

  return { kind: "ok", lastEventAt: site.lastEventAt, eventsToday, redisLast };
}

export async function GET(req: Request): Promise<Response> {
  const t0 = Date.now();
  try {
    const user = await getCurrentUser();
    if (!user) {
      return NextResponse.json({ error: "Unauthorized", reason: "session_expired" }, { status: 401 });
    }

    const { searchParams } = new URL(req.url);
    const brandId = searchParams.get("brandId");
    if (!brandId) {
      return NextResponse.json({ error: "brandId required", reason: "brandId_missing" }, { status: 400 });
    }

    // --- Hot path: zero DB connections ---
    const cached = await readCache(user.id, brandId);
    if (cached) {
      console.info(`[pulse] cache_hit ${Date.now() - t0}ms brandId=${brandId}`);
      return NextResponse.json(cached, { headers: LIVE_CACHE_HEADERS });
    }

    // --- Cold path: DB queries with hard timeout ---
    const todayUTC = new Date();
    todayUTC.setUTCHours(0, 0, 0, 0);

    const result: DbResult = await Promise.race([
      runDbQueries(user.id, brandId, todayUTC),
      new Promise<DbResult>((resolve) =>
        setTimeout(() => resolve({ kind: "timeout" }), DB_TIMEOUT_MS),
      ),
    ]);

    if (result.kind === "timeout") {
      console.warn(`[pulse] db_timeout after ${Date.now() - t0}ms brandId=${brandId}`);
      return NextResponse.json({ error: "DB timeout", reason: "db_timeout" }, { status: 503 });
    }
    if (result.kind === "not_found") {
      return NextResponse.json({ error: "Brand not found", reason: "brand_not_found" }, { status: 404 });
    }
    if (result.kind === "unauthorized") {
      return NextResponse.json({ error: "Access denied", reason: "not_a_member" }, { status: 403 });
    }
    if (result.kind === "no_site") {
      const noSite: PulsePayload = { installed: false, lastEventAt: null, eventsToday: 0 };
      void setCached(pulseResponseKey(user.id, brandId), noSite, CACHE_TTL_S);
      return NextResponse.json(noSite, { headers: LIVE_CACHE_HEADERS });
    }

    // result.kind === "ok"
    // Prefer Redis lastEventAt (updated per-event by collect path)
    // over DB value (updated by hourly cron).
    const dbLastStr = result.lastEventAt?.toISOString() ?? null;
    const lastEventAt =
      result.redisLast && (!dbLastStr || result.redisLast > dbLastStr)
        ? result.redisLast
        : dbLastStr;

    const payload: PulsePayload = {
      installed: true,
      lastEventAt,
      eventsToday: result.eventsToday,
    };
    void setCached(pulseResponseKey(user.id, brandId), payload, CACHE_TTL_S);

    console.info(
      `[pulse] db_fetch ${Date.now() - t0}ms brandId=${brandId}`,
      { eventsToday: result.eventsToday, lastEventAt },
    );
    return NextResponse.json(payload, { headers: LIVE_CACHE_HEADERS });

  } catch (err) {
    const msg = err instanceof Error ? err.message : String(err);
    const isPoolPressure =
      /connection pool|pool_timeout|too many connections|timed out fetching|P1017|P2024|idle.in.transaction|Server has closed the connection/i.test(
        msg,
      );
    console.error(`[pulse] error after ${Date.now() - t0}ms`, { msg, isPoolPressure });
    return NextResponse.json(
      { error: msg, reason: isPoolPressure ? "pool_pressure" : "internal" },
      { status: isPoolPressure ? 503 : 500 },
    );
  }
}
