import { describe, it, expect } from "vitest";
import { barthagWinProbability, getBarthagRating, normalizeTeamName } from "../ncaaw-simulator";

// ─── Log5 win probability formula ────────────────────────────────────────────

describe("barthagWinProbability", () => {
  it("returns 0.5 for equal teams", () => {
    expect(barthagWinProbability(0.7, 0.7)).toBeCloseTo(0.5, 10);
  });

  it("returns >0.5 for team A with higher Barthag", () => {
    expect(barthagWinProbability(0.9, 0.6)).toBeGreaterThan(0.5);
  });

  it("returns <0.5 for team A with lower Barthag", () => {
    expect(barthagWinProbability(0.6, 0.9)).toBeLessThan(0.5);
  });

  it("is symmetric: P(A>B) + P(B>A) = 1", () => {
    const pAB = barthagWinProbability(0.9, 0.4);
    const pBA = barthagWinProbability(0.4, 0.9);
    expect(pAB + pBA).toBeCloseTo(1.0, 10);
  });

  it("satisfies the Barthag definition: barthagWinProbability(x, 0.5) === x", () => {
    // The whole point of Barthag is P(team beats avg D-I opponent) = barthag
    expect(barthagWinProbability(0.9, 0.5)).toBeCloseTo(0.9, 10);
    expect(barthagWinProbability(0.3, 0.5)).toBeCloseTo(0.3, 10);
  });

  it("handles degenerate case: both teams at 0 → coin flip", () => {
    expect(barthagWinProbability(0, 0)).toBe(0.5);
  });

  it("handles degenerate case: both teams at 1 → coin flip", () => {
    expect(barthagWinProbability(1, 1)).toBe(0.5);
  });

  it("near-certain win: strong team (0.9996) vs weak team (0.35)", () => {
    // Connecticut (.9996) vs Norfolk St. (.3468) — very high win probability
    expect(barthagWinProbability(0.9996, 0.3468)).toBeGreaterThan(0.999);
  });

  it("matches the Log5 formula exactly for known values", () => {
    const a = 0.9, b = 0.6;
    const expected = (a * (1 - b)) / (a * (1 - b) + b * (1 - a));
    expect(barthagWinProbability(a, b)).toBeCloseTo(expected, 10);
  });
});

// ─── Team name normalization ──────────────────────────────────────────────────

describe("normalizeTeamName", () => {
  it("converts to lowercase", () => {
    expect(normalizeTeamName("Connecticut")).toBe("connecticut");
  });

  it("trims leading/trailing whitespace", () => {
    expect(normalizeTeamName("  UCLA  ")).toBe("ucla");
  });

  it("collapses internal whitespace", () => {
    expect(normalizeTeamName("South  Carolina")).toBe("south carolina");
  });

  it("preserves punctuation", () => {
    expect(normalizeTeamName("N.C. State")).toBe("n.c. state");
  });
});

// ─── Barthag data lookup ──────────────────────────────────────────────────────

describe("getBarthagRating", () => {
  it("returns correct Barthag for exact known name (lowercase)", () => {
    expect(getBarthagRating("connecticut")).toBeCloseTo(0.9996, 4);
  });

  it("is case-insensitive", () => {
    expect(getBarthagRating("Connecticut")).toBeCloseTo(0.9996, 4);
    expect(getBarthagRating("CONNECTICUT")).toBeCloseTo(0.9996, 4);
  });

  it("resolves uconn alias", () => {
    expect(getBarthagRating("UConn")).toBeCloseTo(0.9996, 4);
  });

  it("returns correct Barthag for #2 team (UCLA)", () => {
    expect(getBarthagRating("UCLA")).toBeCloseTo(0.9991, 4);
  });

  it("handles abbreviated name variants", () => {
    // Both 'michigan st.' and 'michigan state' should resolve
    expect(getBarthagRating("Michigan St.")).toBeCloseTo(0.9817, 4);
    expect(getBarthagRating("Michigan State")).toBeCloseTo(0.9817, 4);
  });

  it("handles 'n.c. state' and 'nc state' aliases", () => {
    expect(getBarthagRating("N.C. State")).toBeCloseTo(0.9766, 4);
    expect(getBarthagRating("nc state")).toBeCloseTo(0.9766, 4);
  });

  it("handles ole miss alias", () => {
    expect(getBarthagRating("Ole Miss")).toBeCloseTo(0.9821, 4);
  });

  it("returns 0.5 for an unknown team name (BARTHAG_FALLBACK = average)", () => {
    expect(getBarthagRating("Fictional University")).toBe(0.5);
  });

  it("returns 0.5 for empty string", () => {
    expect(getBarthagRating("")).toBe(0.5);
  });

  it("returns correct Barthag for a low-ranked team (Norfolk St.)", () => {
    expect(getBarthagRating("Norfolk St.")).toBeCloseTo(0.3468, 4);
    expect(getBarthagRating("Norfolk State")).toBeCloseTo(0.3468, 4);
  });
});

// ─── Bracket path logic ───────────────────────────────────────────────────────

describe("NCAAW bracket advancement path", () => {
  it("R64 matches 1 and 2 feed R32 match 1 (Math.ceil convention)", () => {
    expect(Math.ceil(1 / 2)).toBe(1);
    expect(Math.ceil(2 / 2)).toBe(1);
  });

  it("R64 matches 3 and 4 feed R32 match 2", () => {
    expect(Math.ceil(3 / 2)).toBe(2);
    expect(Math.ceil(4 / 2)).toBe(2);
  });

  it("R64 matches 31 and 32 feed R32 match 16", () => {
    expect(Math.ceil(31 / 2)).toBe(16);
    expect(Math.ceil(32 / 2)).toBe(16);
  });

  it("32 R64 matches produce exactly 16 R32 slots", () => {
    const r32Slots = new Set<number>();
    for (let i = 1; i <= 32; i++) r32Slots.add(Math.ceil(i / 2));
    expect(r32Slots.size).toBe(16);
  });

  it("R32 match i uses 0-indexed winners at (i-1)*2 and (i-1)*2+1", () => {
    for (let i = 1; i <= 16; i++) {
      const p1 = (i - 1) * 2;
      const p2 = (i - 1) * 2 + 1;
      expect(p1).toBeGreaterThanOrEqual(0);
      expect(p2).toBeLessThan(32);
    }
  });

  it("S16 match i uses 0-indexed R32 winners at (i-1)*2 and (i-1)*2+1", () => {
    for (let i = 1; i <= 8; i++) {
      const p1 = (i - 1) * 2;
      const p2 = (i - 1) * 2 + 1;
      expect(p1).toBeGreaterThanOrEqual(0);
      expect(p2).toBeLessThan(16);
    }
  });

  it("E8 match i uses 0-indexed S16 winners at (i-1)*2 and (i-1)*2+1", () => {
    for (let i = 1; i <= 4; i++) {
      const _p1 = (i - 1) * 2;
      const p2 = (i - 1) * 2 + 1;
      expect(p2).toBeLessThan(8);
    }
  });

  it("FF match i uses 0-indexed E8 winners at (i-1)*2 and (i-1)*2+1", () => {
    for (let i = 1; i <= 2; i++) {
      const _p1 = (i - 1) * 2;
      const p2 = (i - 1) * 2 + 1;
      expect(p2).toBeLessThan(4);
    }
  });
});

// ─── Probability bucket math ──────────────────────────────────────────────────

describe("NCAAW placement bucket probability math", () => {
  const N = 50_000;

  it("champion: count/N = 1.0 when a team wins every simulation", () => {
    expect(N / N).toBeCloseTo(1.0, 10);
  });

  it("FF loser slots: count/(2*N) sums to 1.0 across teams whose counts total 2*N", () => {
    // 2 FF losers per sim → total across all teams = 2*N
    const counts = [N, N];
    const sum = counts.reduce((s, c) => s + c / (2 * N), 0);
    expect(sum).toBeCloseTo(1.0, 10);
  });

  it("E8 loser slots: count/(4*N) sums to 1.0 across teams whose counts total 4*N", () => {
    const counts = [N, N, N, N];
    const sum = counts.reduce((s, c) => s + c / (4 * N), 0);
    expect(sum).toBeCloseTo(1.0, 10);
  });

  it("probThird equals probFourth for the same team (same ff count / 2N)", () => {
    const ffCount = 12500;
    const probThird = ffCount / (2 * N);
    const probFourth = ffCount / (2 * N);
    expect(probThird).toBe(probFourth);
  });

  it("probFifth through probEighth are equal for the same team (same e8 count / 4N)", () => {
    const e8Count = 6250;
    const probs = [e8Count / (4 * N), e8Count / (4 * N), e8Count / (4 * N), e8Count / (4 * N)];
    expect(probs[0]).toBe(probs[1]);
    expect(probs[1]).toBe(probs[2]);
    expect(probs[2]).toBe(probs[3]);
  });
});

// ─── EV alignment with scoring rules ─────────────────────────────────────────

describe("NCAAW EV calculation alignment with scoring rules", () => {
  // DEFAULT_SCORING_RULES: Champion=100, Finalist=70, FF loser=45, E8 loser=20, rest=0
  // (sum = 340; must match DEFAULT_SCORING_RULES in admin.sports-seasons.$id.simulate.tsx)
  const SCORING = {
    first: 100,
    second: 70,
    thirdFourth: 45,
    fifthToEighth: 20,
  };

  function computeEV(probs: {
    probFirst: number; probSecond: number;
    probThird: number; probFourth: number;
    probFifth: number; probSixth: number; probSeventh: number; probEighth: number;
  }): number {
    return (
      probs.probFirst   * SCORING.first +
      probs.probSecond  * SCORING.second +
      probs.probThird   * SCORING.thirdFourth +
      probs.probFourth  * SCORING.thirdFourth +
      probs.probFifth   * SCORING.fifthToEighth +
      probs.probSixth   * SCORING.fifthToEighth +
      probs.probSeventh * SCORING.fifthToEighth +
      probs.probEighth  * SCORING.fifthToEighth
    );
  }

  it("champion with probFirst=1 earns 100 EV", () => {
    const probs = {
      probFirst: 1, probSecond: 0, probThird: 0, probFourth: 0,
      probFifth: 0, probSixth: 0, probSeventh: 0, probEighth: 0,
    };
    expect(computeEV(probs)).toBeCloseTo(100, 10);
  });

  it("finalist with probSecond=1 earns 70 EV", () => {
    const probs = {
      probFirst: 0, probSecond: 1, probThird: 0, probFourth: 0,
      probFifth: 0, probSixth: 0, probSeventh: 0, probEighth: 0,
    };
    expect(computeEV(probs)).toBeCloseTo(70, 10);
  });

  it("confirmed FF loser (probThird=probFourth=0.5 each) earns 45 EV", () => {
    const probs = {
      probFirst: 0, probSecond: 0, probThird: 0.5, probFourth: 0.5,
      probFifth: 0, probSixth: 0, probSeventh: 0, probEighth: 0,
    };
    expect(computeEV(probs)).toBeCloseTo(45, 10);
  });

  it("confirmed E8 loser (probFifth–Eighth=0.25 each) earns 20 EV", () => {
    const probs = {
      probFirst: 0, probSecond: 0, probThird: 0, probFourth: 0,
      probFifth: 0.25, probSixth: 0.25, probSeventh: 0.25, probEighth: 0.25,
    };
    expect(computeEV(probs)).toBeCloseTo(20, 10);
  });

  it("R64 loser (all zeros) earns 0 EV", () => {
    const probs = {
      probFirst: 0, probSecond: 0, probThird: 0, probFourth: 0,
      probFifth: 0, probSixth: 0, probSeventh: 0, probEighth: 0,
    };
    expect(computeEV(probs)).toBe(0);
  });
});
