/**
 * GET /api/live
 *
 * Public-safe payer activity feed for the /live board.
 *
 * Returns the same data as /api/admin/recent-payers but with:
 *   - Payer addresses truncated to 0xABCD…EF12 form
 *   - No admin token required
 *   - Limited to 50 most recent
 *   - Heartbeat freshness flag so the board can render a stale-data warning
 *
 * All data is already public on-chain. We truncate addresses on the public
 * surface as a courtesy nod to pseudonymity — click-through to the
 * explorer is always one hop away if someone wants the full address.
 *
 * Cache: no-store + dynamic forces fresh reads on every request. Next.js
 * will not cache this response, so every visitor sees the latest entries.
 */

import { NextResponse } from 'next/server';
import {
  getRecentPaidCalls,
  getLifetimeTotals,
  readHeartbeat,
  isPersistenceConfigured,
  type PayerEntry,
} from '../_lib/payer-log';

export const dynamic = 'force-dynamic';
export const revalidate = 0;

// Tiered freshness model — honest about real x402 traffic patterns.
// Most agents pay sporadically; a 30-min gap is a normal idle period, not
// a system failure. Only show "stale" warning when truly old.
const FRESH_GREEN_MS = 5 * 60 * 1000;     // <5min: live, traffic flowing
const FRESH_YELLOW_MS = 60 * 60 * 1000;   // <1h: idle but recent
// > 1h: cold—could be normal idle or could be broken; UI says "check back"
const PUBLIC_LIMIT = 50;

type FreshnessLevel = 'live' | 'idle' | 'cold' | 'never';

function freshnessLevel(ageMs: number | null): FreshnessLevel {
  if (ageMs === null) return 'never';
  if (ageMs < FRESH_GREEN_MS) return 'live';
  if (ageMs < FRESH_YELLOW_MS) return 'idle';
  return 'cold';
}

function truncate(addr: string): string {
  if (!addr || typeof addr !== 'string') return 'unknown';
  if (addr.length < 12) return addr;
  return `${addr.slice(0, 6)}…${addr.slice(-4)}`;
}

function publicEntry(e: PayerEntry) {
  return {
    ts: e.ts,
    iso: new Date(e.ts).toISOString(),
    path: e.path,
    payer: truncate(e.payer),
    payerFull: undefined, // redacted on public surface
    price: e.price,
    chain: e.chain,
    status: e.status,
  };
}

export async function GET(): Promise<NextResponse> {
  if (!isPersistenceConfigured()) {
    return NextResponse.json(
      {
        configured: false,
        recent: [],
        lifetime: { calls: 0, usdc: '0.000000' },
        freshness: { level: 'never' as FreshnessLevel, ageMs: null, lastIso: null },
        message: 'Live data layer is being provisioned. Check back shortly.',
      },
      { status: 200, headers: { 'Cache-Control': 'no-store' } },
    );
  }

  const [recent, lifetime, heartbeat] = await Promise.all([
    getRecentPaidCalls(PUBLIC_LIMIT),
    getLifetimeTotals(),
    readHeartbeat(),
  ]);

  // The heartbeat reflects the last paid call (self-heartbeat in recordPaidCall)
  // OR the last manual /api/cron/heartbeat probe — whichever was more recent.
  const heartbeatAgeMs = heartbeat ? Date.now() - heartbeat : null;
  const level = freshnessLevel(heartbeatAgeMs);

  // Compute "last paid call age" so the board can show "X seconds ago".
  const lastCallAgeMs = recent[0]?.ts ? Date.now() - recent[0].ts : null;

  // Unique payers seen in the recent window (truncated for public surface).
  const seen = new Set<string>();
  for (const e of recent) {
    if (e.payer) seen.add(e.payer.toLowerCase());
  }

  return NextResponse.json(
    {
      configured: true,
      generatedAt: new Date().toISOString(),
      freshness: {
        level,
        ageMs: heartbeatAgeMs,
        lastIso: heartbeat ? new Date(heartbeat).toISOString() : null,
      },
      lifetime: {
        calls: lifetime.calls,
        usdc: (lifetime.usdcMicro / 1_000_000).toFixed(6),
      },
      recentWindow: {
        size: recent.length,
        uniquePayers: seen.size,
        lastCallAgeMs,
      },
      recent: recent.map(publicEntry),
    },
    { status: 200, headers: { 'Cache-Control': 'no-store' } },
  );
}
