'use strict';

/**
 * /api/ledger — the truth surface (Sessions 55 + 58).
 *
 *   GET /accuracy — grade-tier buckets (Session 55, snapshot-log based).
 *   GET /mine     — the caller's own ledger_entries, newest first (Phase 1).
 *   GET /model    — the PUBLIC model record (user_id NULL rows) + the 30d
 *                   aggregate {hit_pct, beat_close_pct, pending}. Percentages
 *                   are null below MIN_AGG_SAMPLE (20) — the UI renders
 *                   "RECORD BUILDING" instead. Never a % on a small n.
 */

const express = require('express');
const { createRateLimit } = require('../middleware/rateLimit');
const { requireAuth } = require('../middleware/auth');
const outcomeService = require('../services/outcomeService');
const ledgerService = require('../services/ledgerService');
const { nameKey } = require('../utils/playerName');

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

const VALID_SPORTS = new Set(['nba', 'mlb', 'wnba', 'soccer']);
const VALID_TIERS = new Set(['A', 'B', 'C', 'D', 'F']);
const ROW_COLUMNS = 'id, player_key, player_name, sport, stat, line, side, locked_odds, book, grade, edge, confidence, model_value, graded_at, game_id, game_date, closing_line, closing_odds, clv, clv_result, outcome, actual_value, settled_at, revised_from_grade';

// Session 64 — DIRECTIONAL-CLV COLUMNS ARE SERVER-GATED.
// They are appended to the SELECT only for a tier entitled to see them, so a
// Free request never RECEIVES the data — it is not sent-and-hidden, and there
// is no client/CSS gate to bypass. `dclv_fair_*` stay server-side entirely:
// they are the de-vig internals, not the receipt.
const CLV_COLUMNS = 'dclv, dclv_state, dclv_computed_at';
const { canAccess } = require('../config/tiers');

/** Columns for this caller. Free gets the base list, unchanged. */
function columnsFor(req) {
  const tier = (req && req.user && req.user.tier) || 'free';
  return canAccess(tier, 'clv_badge') ? `${ROW_COLUMNS}, ${CLV_COLUMNS}` : ROW_COLUMNS;
}

/** Defence in depth: strip CLV keys from any row on an unentitled response,
 *  so a future SELECT change cannot quietly start leaking. */
function stripClv(rows, req) {
  const tier = (req && req.user && req.user.tier) || 'free';
  if (canAccess(tier, 'clv_badge')) return rows || [];
  return (rows || []).map((r) => {
    const { dclv, dclv_state, dclv_computed_at, dclv_fair_lock, dclv_fair_close, ...rest } = r || {};
    return rest;
  });
}

function sbOrNull() {
  try {
    if (!ledgerService.__internals.isConfigured()) return null;
    return require('../utils/supabase').getSupabaseServiceClient();
  } catch { return null; }
}

function applyFilters(query, req) {
  const sport = String(req.query.sport || '').toLowerCase();
  const tier = String(req.query.tier || req.query.grade || '').toUpperCase();
  if (VALID_SPORTS.has(sport)) query = query.eq('sport', sport);
  if (VALID_TIERS.has(tier)) query = query.ilike('grade', `${tier}%`);
  return query;
}

router.get('/accuracy', async (req, res) => {
  // Truth-Everywhere Part 2 (item 7) — read the CLEAN Postgres ledger aggregate
  // (model_value > 0 excludes degraded rows), NOT the Redis outcome log (which
  // still counts degraded projection-0 outcomes and can't be filtered).
  try {
    const agg = await ledgerService.getModelAggregate({});
    const buckets = ledgerService.accuracyBucketsFromAgg(agg);
    const overall = {
      hits: agg.hits, misses: agg.misses, pushes: agg.pushes,
      total: agg.hits + agg.misses + agg.pushes, pct: agg.hit_pct ?? null,
    };
    res.set('Cache-Control', 'public, max-age=300');
    return res.json({ buckets, overall, updated_at: null });
  } catch (err) {
    console.error('[ledger/accuracy]', err.message);
    return res.status(200).json({ buckets: [] });
  }
});

// The caller's own reads. Service-role read scoped to req.user.id — clients
// never query the table directly (RLS blocks writes; reads go through here).
router.get('/mine', requireAuth, async (req, res) => {
  const sb = sbOrNull();
  if (!sb) return res.json({ entries: [] });
  try {
    const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 100));
    let q = sb.from('ledger_entries')
      .select(columnsFor(req))
      .eq('user_id', req.user.id);
    q = applyFilters(q, req);
    const { data, error } = await q
      .order('graded_at', { ascending: false })
      .limit(limit);
    if (error) throw new Error(error.message);
    return res.json({ entries: stripClv(data, req) });
  } catch (err) {
    console.error('[ledger/mine]', err.message);
    return res.status(200).json({ entries: [] });
  }
});

// The public model record: every pipeline pre-grade, settled or pending,
// misses included. This is the product's proof surface.
router.get('/model', async (req, res) => {
  const sb = sbOrNull();
  try {
    const aggregate = await ledgerService.getModelAggregate({
      sport: VALID_SPORTS.has(String(req.query.sport || '').toLowerCase())
        ? String(req.query.sport).toLowerCase() : undefined,
      // VYNDR-on-player (work-order 1.4/5.2): per-player public record.
      playerKey: req.query.player ? nameKey(String(req.query.player).slice(0, 60)) : undefined,
      // VYNDR-on-team (5.2, migration 020): exact statsapi team name.
      team: req.query.team ? String(req.query.team).slice(0, 60) : undefined,
    });
    let entries = [];
    if (sb) {
      const limit = Math.min(200, Math.max(1, Number(req.query.limit) || 60));
      let q = sb.from('ledger_entries')
        .select(ROW_COLUMNS)
        .is('user_id', null);
      q = applyFilters(q, req);
      // Session 60 (night2/E) — PRIOR READS: a player's own public history.
      if (req.query.player) q = q.eq('player_key', nameKey(String(req.query.player).slice(0, 60)));
      // Session 64 (A1-S5) — the Yesterday results surface: rows for one game date.
      if (req.query.date && /^\d{4}-\d{2}-\d{2}$/.test(String(req.query.date))) {
        q = q.eq('game_date', String(req.query.date));
      }
      const { data, error } = await q
        .order('graded_at', { ascending: false })
        .limit(limit);
      if (error) throw new Error(error.message);
      entries = data || [];
    }
    res.set('Cache-Control', 'public, max-age=60');
    return res.json({ entries, aggregate, min_sample: ledgerService.MIN_AGG_SAMPLE });
  } catch (err) {
    console.error('[ledger/model]', err.message);
    return res.status(200).json({ entries: [], aggregate: null, min_sample: ledgerService.MIN_AGG_SAMPLE });
  }
});

module.exports = router;
