import { allSnapshots, hasSnapshot, saveSnapshot, oddsFor } from './db/db';
import { deVig } from '../../src/lib/odds';
import { brierScore, outcomeOfScore, rpsScore } from '../../src/lib/model/scoring';
import type { TournamentState } from './tournament';
import type { ModelEngine } from './model';
import type { MarketSnapshot, MatchProbs, ScoreboardData, ScoreboardRow } from '../../src/lib/types';

// The Model-vs-Market scoreboard. Fairness rules:
//  - the model's forecast is FROZEN at most SNAPSHOT_BEFORE_MS before kickoff
//    (whatever the model believed then — no hindsight);
//  - the market benchmark is the latest captured pre-kickoff bookmaker line,
//    de-vigged to fair probabilities;
//  - snapshots taken after kickoff are stored but flagged `late` and excluded
//    from the head-to-head totals.

const SNAPSHOT_BEFORE_MS = 15 * 60 * 1000;

function latestPreKickoffMarket(num: number, kickoffT: number): MarketSnapshot | null {
  const history = oddsFor(num).filter((o) => o.captured_at <= kickoffT + 60_000);
  const last = history[history.length - 1];
  if (!last) return null;
  const probs = deVig(last.home_ml, last.draw_ml, last.away_ml);
  if (!probs) return null;
  return {
    provider: last.provider,
    probs,
    homeMl: last.home_ml!,
    drawMl: last.draw_ml!,
    awayMl: last.away_ml!,
  };
}

/** Freeze forecasts for any fixture at/inside the snapshot window. Runs on every
 *  live tick — cheap (indexed point lookups, no network). */
export function snapshotCheck(state: TournamentState, model: ModelEngine): number {
  const now = Date.now();
  let taken = 0;
  for (const f of state.allFixtures()) {
    if (!f.home.team || !f.away.team) continue;
    const kickoffT = new Date(f.kickoff).getTime();
    // Inside the pre-kickoff window — or the match already started regardless of
    // the scheduled clock (reschedules); the `late` flag keeps totals honest.
    if (now < kickoffT - SNAPSHOT_BEFORE_MS && f.status === 'scheduled') continue;
    if (hasSnapshot(f.num)) continue;
    const pred = model.current().matches.find((m) => m.num === f.num);
    if (!pred) continue; // finished before we ever saw it — nothing honest to freeze
    const late = f.status !== 'scheduled';
    saveSnapshot(f.num, late, { probs: pred.probs, lambdaHome: pred.lambdaHome, lambdaAway: pred.lambdaAway }, latestPreKickoffMarket(f.num, kickoffT));
    taken++;
  }
  return taken;
}

export function buildScoreboard(state: TournamentState): ScoreboardData {
  const byNum = new Map(state.allFixtures().map((f) => [f.num, f]));
  const rows: ScoreboardRow[] = [];
  let n = 0, mR = 0, kR = 0, mB = 0, kB = 0, mCloser = 0, kCloser = 0;

  for (const s of allSnapshots()) {
    const f = byNum.get(s.fixture_num);
    if (!f || !f.home.team || !f.away.team) continue;
    const modelStored = JSON.parse(s.model_json) as { probs: MatchProbs };
    const market = s.market_json ? (JSON.parse(s.market_json) as MarketSnapshot) : null;

    let scored: ScoreboardRow['scored'] = null;
    if (f.status === 'finished' && f.homeScore != null && f.awayScore != null) {
      const o = outcomeOfScore(f.homeScore, f.awayScore);
      scored = {
        outcome: o,
        modelRps: +rpsScore(modelStored.probs, o).toFixed(4),
        marketRps: market ? +rpsScore(market.probs, o).toFixed(4) : null,
        modelBrier: +brierScore(modelStored.probs, o).toFixed(4),
        marketBrier: market ? +brierScore(market.probs, o).toFixed(4) : null,
      };
      if (!s.late && market && scored.marketRps != null) {
        n++;
        mR += scored.modelRps; kR += scored.marketRps;
        mB += scored.modelBrier; kB += scored.marketBrier!;
        if (scored.modelRps < scored.marketRps) mCloser++;
        else if (scored.marketRps < scored.modelRps) kCloser++;
      }
    }

    rows.push({
      num: f.num,
      home: f.home.team,
      away: f.away.team,
      group: f.group,
      kickoff: f.kickoff,
      status: f.status,
      homeScore: f.homeScore,
      awayScore: f.awayScore,
      takenAt: s.taken_at,
      late: !!s.late,
      model: modelStored.probs,
      market,
      scored,
    });
  }

  rows.sort((a, b) => a.kickoff.localeCompare(b.kickoff));
  return {
    rows,
    totals: n
      ? {
          n,
          modelRps: +(mR / n).toFixed(4), marketRps: +(kR / n).toFixed(4),
          modelBrier: +(mB / n).toFixed(4), marketBrier: +(kB / n).toFixed(4),
          modelCloser: mCloser, marketCloser: kCloser,
        }
      : null,
  };
}
