'use strict';

/**
 * GET /api/snapshot/:sport (Session 45) — the latest pre-graded slate.
 *
 * Public, cache-only read of `snapshot:{sport}:latest` (enriched grades with
 * archetype + gradedAt, plus line deltas). Falls back to the `grades:{sport}`
 * envelope when no snapshot has run yet. NEVER triggers a snapshot (that's the
 * internal cron's job) — so it can't drain the PropLine quota.
 */

const express = require('express');
const { createRateLimit } = require('../middleware/rateLimit');
const { cacheGet } = require('../utils/redis');
const { nameKey } = require('../utils/playerName');
// S6 (A1 board) — ●●○●● last-10 vs tonight's locked line, computed from the
// rosterlogs blob the snapshot pipeline already writes. Pure, cache-only.
const { indexRosterLogs, attachLast10Dots } = require('../services/last10Dots');
// Session 67 — the model price never leaves the server for an unentitled
// viewer. This endpoint is PUBLIC, so the Session-66 gate on /api/analyze was
// being bypassed here on every graded row. Same layer as the CLV gate.
const { stripModelPrice } = require('../utils/snapshotGating');
const { resolveTierFromRequest } = require('../utils/requestTier');

const router = express.Router();
router.use(createRateLimit({ windowMs: 60_000, max: 60 }));

// Session 55 — overlay settled outcomes (self-learning loop) onto the grades so
// a completed prop can render "✅ HIT (2)" / "❌ MISS". Keyed by player+stat+line+side.
function outcomeIndex(log) {
  const map = {};
  for (const o of Array.isArray(log) ? log : []) {
    const side = String(o.side || 'O').toUpperCase() === 'U' ? 'U' : 'O';
    map[`${nameKey(o.player)}|${String(o.stat).toLowerCase()}|${o.line}|${side}`] = o;
  }
  return map;
}
function attachOutcomes(grades, index) {
  if (!index || Object.keys(index).length === 0) return grades;
  return grades.map((g) => {
    const side = String(g.direction || 'over').toLowerCase() === 'under' ? 'U' : 'O';
    const o = index[`${nameKey(g.player || g.player_name)}|${String(g.stat_type || g.stat || '').toLowerCase()}|${g.line}|${side}`];
    return o ? { ...g, outcome: { result: o.result, actual: o.actual } } : g;
  });
}

// Session 57 (Phase 0) — GET /api/snapshot/summary: the HeartbeatBar's honest
// data source. Cheap cache-only Redis reads; total graded props in the current
// snapshots + the latest pipeline run time. Registered BEFORE /:sport or
// Express captures "summary" as a sport.
const SUMMARY_SPORTS = ['nba', 'wnba', 'mlb', 'soccer'];

router.get('/summary', async (req, res) => {
  try {
    const reads = await Promise.all(SUMMARY_SPORTS.map(async (sp) => {
      const snap = await cacheGet(`snapshot:${sp}:latest`);
      if (snap && Array.isArray(snap.grades)) {
        return {
          sport: sp,
          graded: snap.grades.length,
          updated_at: snap.updated_at || null,
          refreshed_at: snap.refreshed_at || snap.updated_at || null,
        };
      }
      const env = await cacheGet(`grades:${sp}`);
      return {
        sport: sp,
        graded: env && Array.isArray(env.grades) ? env.grades.length : 0,
        updated_at: (env && env.updated_at) || null,
        refreshed_at: (env && (env.refreshed_at || env.updated_at)) || null,
      };
    }));
    const graded = reads.reduce((n, r) => n + r.graded, 0);
    // ISO timestamps sort lexicographically — the max is the latest run.
    // `updated_at` = last grade LOCK (5×/day, intentionally stable). `refreshed_at`
    // = last freshness heartbeat (grade lock OR intraday refresh, every ~20 min).
    // The SYNC badge measures against refreshed_at; updated_at is exposed so the
    // UI can distinguish "grades locked at X" from "lines synced at Y".
    const updated_at = reads.map((r) => r.updated_at).filter(Boolean).sort().pop() || null;
    const refreshed_at = reads.map((r) => r.refreshed_at).filter(Boolean).sort().pop() || updated_at;
    const sports = {};
    for (const r of reads) sports[r.sport] = r.graded;
    // Session 58 (Task 5) — the SYNC badge thresholds key off the pipeline's
    // EXPECTED cadence, not a flat 5 minutes: normal < 1.5x, amber ≥ 1.5x,
    // STALE red ≥ 3x. Default = the 5h max cron gap; when Phase 2.5's
    // intraday refresh ships, drop the env value and the badge goes live
    // with zero UI changes.
    const expected_interval_s = Number(process.env.SNAPSHOT_EXPECTED_INTERVAL) > 0
      ? Number(process.env.SNAPSHOT_EXPECTED_INTERVAL) : 18000;
    res.set('Cache-Control', 'public, max-age=30');
    return res.json({ graded, updated_at, refreshed_at, sports, expected_interval_s });
  } catch (err) {
    console.error('[snapshot/summary]', err.message);
    return res.status(200).json({ graded: 0, updated_at: null, sports: {} });
  }
});

router.get('/:sport', async (req, res) => {
  const sport = String(req.params.sport || '').toLowerCase();
  try {
    // Tier is resolved from the bearer token when one is present; anonymous
    // and free callers get the market legs only. Because the response now
    // VARIES by entitlement, the shared `public` cache directive below is
    // downgraded to `private` for authenticated callers — a CDN must never
    // hand a paid payload to an anonymous viewer.
    const tier = await resolveTierFromRequest(req);
    const gate = (grades) => stripModelPrice(grades, tier);
    const cacheHeader = req.headers.authorization ? 'private, max-age=30' : 'public, max-age=30';
    const [snap, outcomeLog, rosterBlob] = await Promise.all([
      cacheGet(`snapshot:${sport}:latest`),
      cacheGet(`outcomes:${sport}:log`),
      cacheGet(`rosterlogs:${sport}`),
    ]);
    const idx = outcomeIndex(outcomeLog);
    const roster = indexRosterLogs(rosterBlob);
    const enrich = (grades) => attachLast10Dots(attachOutcomes(grades, idx), roster, sport);
    if (snap && Array.isArray(snap.grades)) {
      res.set('Cache-Control', cacheHeader);
      return res.json({ sport, updated_at: snap.updated_at, refreshed_at: snap.refreshed_at || snap.updated_at || null, grades: gate(enrich(snap.grades)), deltas: snap.deltas || [] });
    }
    // Fallback: the grades envelope (no deltas yet).
    const env = await cacheGet(`grades:${sport}`);
    const grades = env && Array.isArray(env.grades) ? env.grades : [];
    res.set('Cache-Control', cacheHeader);
    return res.json({ sport, updated_at: env && env.updated_at, grades: gate(enrich(grades)), deltas: [] });
  } catch (err) {
    console.error('[snapshot]', err.message);
    return res.status(200).json({ sport, grades: [], deltas: [] });
  }
});

module.exports = router;
