import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { buildSimulator } from './monteCarlo';
import type { RatingsModel } from './predict';
import type { FixturesFile } from '../types';

const DATA = join(process.cwd(), 'public', 'data');
const fixtures = (JSON.parse(readFileSync(join(DATA, 'fixtures.json'), 'utf8')) as FixturesFile).fixtures;
const model = JSON.parse(readFileSync(join(DATA, 'ratings.json'), 'utf8')) as RatingsModel;

/** Deterministic RNG so the test is stable. */
function mulberry32(seed: number): () => number {
  let a = seed;
  return () => {
    a |= 0; a = (a + 0x6d2b79f5) | 0;
    let t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

describe('monte carlo tournament', () => {
  const sim = buildSimulator(fixtures, model);
  const res = sim.run(3000, mulberry32(12345));
  const byTeam = new Map(res.teams.map((t) => [t.team, t]));
  const sum = (k: keyof (typeof res.teams)[number]) =>
    res.teams.reduce((s, t) => s + (t[k] as number), 0);

  it('simulates all 48 teams', () => {
    expect(res.teams.length).toBe(48);
  });

  it('conserves the exact per-round counts (12/32/16/8/4/2/1)', () => {
    expect(sum('winGroup')).toBeCloseTo(12, 5);
    expect(sum('qualify')).toBeCloseTo(32, 5);
    expect(sum('reachR16')).toBeCloseTo(16, 5);
    expect(sum('reachQF')).toBeCloseTo(8, 5);
    expect(sum('reachSF')).toBeCloseTo(4, 5);
    expect(sum('reachFinal')).toBeCloseTo(2, 5);
    expect(sum('champion')).toBeCloseTo(1, 5);
  });

  it('every team has monotonically non-increasing deep-run odds', () => {
    for (const t of res.teams) {
      expect(t.qualify).toBeGreaterThanOrEqual(t.reachR16 - 1e-9);
      expect(t.reachR16).toBeGreaterThanOrEqual(t.reachQF - 1e-9);
      expect(t.reachQF).toBeGreaterThanOrEqual(t.reachSF - 1e-9);
      expect(t.reachSF).toBeGreaterThanOrEqual(t.reachFinal - 1e-9);
      expect(t.reachFinal).toBeGreaterThanOrEqual(t.champion - 1e-9);
      expect(t.winGroup).toBeGreaterThanOrEqual(t.champion - 1e-9);
    }
  });

  it('all probabilities are within [0, 1]', () => {
    for (const t of res.teams) {
      for (const k of ['winGroup', 'qualify', 'reachR16', 'reachQF', 'reachSF', 'reachFinal', 'champion'] as const) {
        expect(t[k]).toBeGreaterThanOrEqual(0);
        expect(t[k]).toBeLessThanOrEqual(1);
      }
    }
  });

  it('ranks a strong team well above a weak one', () => {
    const strong = byTeam.get('Spain')!;
    const weak = byTeam.get('Curaçao')!;
    expect(strong.champion).toBeGreaterThan(weak.champion);
    expect(strong.qualify).toBeGreaterThan(weak.qualify);
    // the favourite should have a non-trivial title chance
    expect(res.teams[0]!.champion).toBeGreaterThan(0.04);
  });
});
