import { describe, it, expect, vi, beforeEach } from "vitest";
import { simGroupMatch, WorldCupSimulator } from "../world-cup-simulator";

vi.mock("~/lib/logger", () => ({
  logger: { warn: vi.fn(), info: vi.fn(), error: vi.fn(), debug: vi.fn() },
}));

// ─── Pure math: simGroupMatch ─────────────────────────────────────────────────

describe("simGroupMatch", () => {
  it("returns win, draw, or loss", () => {
    const results = new Set<string>();
    for (let i = 0; i < 300; i++) {
      results.add(simGroupMatch(1800, 1600));
    }
    // All three outcomes should appear in 300 trials
    expect(results.has("win")).toBe(true);
    expect(results.has("draw")).toBe(true);
    expect(results.has("loss")).toBe(true);
  });

  it("equal teams draw ≈28% of the time", () => {
    let draws = 0;
    const N = 5_000;
    for (let i = 0; i < N; i++) {
      if (simGroupMatch(1500, 1500) === "draw") draws++;
    }
    const drawRate = draws / N;
    // BASE_DRAW_RATE = 0.28 at eloDiff=0; accept ±5% from sampling noise at N=5k
    expect(drawRate).toBeGreaterThan(0.23);
    expect(drawRate).toBeLessThan(0.33);
  });

  it("draw rate decays with large Elo gap", () => {
    let draws = 0;
    const N = 5_000;
    for (let i = 0; i < N; i++) {
      if (simGroupMatch(2000, 1400) === "draw") draws++;
    }
    const drawRate = draws / N;
    // eloDiff = 600 → pDraw ≈ 0.28 * exp(-1.2) ≈ 0.084; accept ±5%
    expect(drawRate).toBeLessThan(0.15);
  });

  it("strong favorite wins more often than underdog", () => {
    let wins = 0;
    let losses = 0;
    const N = 5_000;
    for (let i = 0; i < N; i++) {
      const r = simGroupMatch(1900, 1600);
      if (r === "win") wins++;
      if (r === "loss") losses++;
    }
    expect(wins).toBeGreaterThan(losses);
  });

  it("results sum to 1.0 (no probability mass lost)", () => {
    let wins = 0, draws = 0, losses = 0;
    const N = 10_000;
    for (let i = 0; i < N; i++) {
      const r = simGroupMatch(1700, 1700);
      if (r === "win") wins++;
      else if (r === "draw") draws++;
      else losses++;
    }
    expect(wins + draws + losses).toBe(N);
  });
});

// ─── WorldCupSimulator (with mocked DB) ───────────────────────────────────────

// Build 48 mock participants (ids p0..p47)
function makeParticipants(count = 48) {
  return Array.from({ length: count }, (_, i) => ({
    id: `p${i}`,
    name: `Team${i}`,
    sportsSeasonId: "season-1",
  }));
}

vi.mock("~/database/context", () => ({
  database: vi.fn(),
}));

import { database } from "~/database/context";

const mockDb = {
  query: {
    participants: { findMany: vi.fn() },
    scoringEvents: { findFirst: vi.fn() },
    tournamentGroups: { findMany: vi.fn() },
    playoffMatches: { findMany: vi.fn() },
  },
  select: vi.fn().mockReturnThis(),
  from: vi.fn().mockReturnThis(),
  where: vi.fn().mockResolvedValue([]),
};

beforeEach(() => {
  vi.mocked(database).mockReturnValue(mockDb as never);
  mockDb.query.participants.findMany.mockReset();
  mockDb.query.scoringEvents.findFirst.mockReset();
  mockDb.query.tournamentGroups.findMany.mockReset();
  mockDb.query.playoffMatches.findMany.mockReset();
  mockDb.select.mockReturnValue(mockDb);
  mockDb.from.mockReturnValue(mockDb);
  mockDb.where.mockResolvedValue([]);
});

describe("WorldCupSimulator", () => {
  it("throws when no participants are found", async () => {
    mockDb.query.participants.findMany.mockResolvedValue([]);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    await expect(sim.simulate("season-1")).rejects.toThrow("No participants found");
  });

  it("returns one result per participant", async () => {
    const participants = makeParticipants(48);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");
    expect(results).toHaveLength(48);
    const ids = new Set(results.map((r) => r.participantId));
    for (const p of participants) {
      expect(ids.has(p.id)).toBe(true);
    }
  });

  it("column sums for champion, runner-up, 3rd, 4th are each ≈1.0", async () => {
    const participants = makeParticipants(48);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");

    const sumFirst  = results.reduce((s, r) => s + r.probabilities.probFirst,  0);
    const sumSecond = results.reduce((s, r) => s + r.probabilities.probSecond, 0);
    const sumThird  = results.reduce((s, r) => s + r.probabilities.probThird,  0);
    const sumFourth = results.reduce((s, r) => s + r.probabilities.probFourth, 0);

    expect(sumFirst).toBeCloseTo(1.0, 2);
    expect(sumSecond).toBeCloseTo(1.0, 2);
    expect(sumThird).toBeCloseTo(1.0, 2);
    expect(sumFourth).toBeCloseTo(1.0, 2);
  });

  it("probabilities are all non-negative", async () => {
    const participants = makeParticipants(48);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");

    for (const r of results) {
      const { probFirst, probSecond, probThird, probFourth, probFifth } = r.probabilities;
      expect(probFirst).toBeGreaterThanOrEqual(0);
      expect(probSecond).toBeGreaterThanOrEqual(0);
      expect(probThird).toBeGreaterThanOrEqual(0);
      expect(probFourth).toBeGreaterThanOrEqual(0);
      expect(probFifth).toBeGreaterThanOrEqual(0);
    }
  });

  it("SF losers land in 3rd or 4th, never 1st or 2nd", async () => {
    // Set up 8 participants (small bracket, 2 groups of 4)
    const participants = makeParticipants(8);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");

    // probFirst + probSecond + probThird + probFourth should cover all probability mass
    // No team should have probThird or probFourth < 0
    for (const r of results) {
      expect(r.probabilities.probFirst).toBeGreaterThanOrEqual(0);
      expect(r.probabilities.probThird).toBeGreaterThanOrEqual(0);
      expect(r.probabilities.probFourth).toBeGreaterThanOrEqual(0);
      // A champion should have 0 chance at 3rd/4th AND vice versa
      // (not guaranteed in aggregate but probFirst + probThird can't both be 1)
      expect(r.probabilities.probFirst + r.probabilities.probThird).toBeLessThanOrEqual(1.01);
    }
  });

  it("a team with pre-completed group stage result is fixed in simulation", async () => {
    const participants = makeParticipants(48);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue({ id: "event-1" });

    // One group fully complete: p0 wins everything, p3 loses everything
    const group = {
      id: "group-a",
      groupName: "A",
      scoringEventId: "event-1",
      members: [
        { participantId: "p0" }, { participantId: "p1" },
        { participantId: "p2" }, { participantId: "p3" },
      ],
      matches: [
        { participant1Id: "p0", participant2Id: "p1", participant1Score: 3, participant2Score: 0, isComplete: true, matchday: 1 },
        { participant1Id: "p2", participant2Id: "p3", participant1Score: 2, participant2Score: 0, isComplete: true, matchday: 1 },
        { participant1Id: "p0", participant2Id: "p2", participant1Score: 2, participant2Score: 0, isComplete: true, matchday: 2 },
        { participant1Id: "p1", participant2Id: "p3", participant1Score: 1, participant2Score: 0, isComplete: true, matchday: 2 },
        { participant1Id: "p0", participant2Id: "p3", participant1Score: 1, participant2Score: 0, isComplete: true, matchday: 3 },
        { participant1Id: "p1", participant2Id: "p2", participant1Score: 1, participant2Score: 1, isComplete: true, matchday: 3 },
      ],
    };

    // Remaining 44 participants in 11 synthetic groups
    const remainingGroups = Array.from({ length: 11 }, (_, gi) => ({
      id: `group-${gi + 2}`,
      groupName: String.fromCharCode(66 + gi),
      scoringEventId: "event-1",
      members: [
        { participantId: `p${(gi + 1) * 4 + 0}` },
        { participantId: `p${(gi + 1) * 4 + 1}` },
        { participantId: `p${(gi + 1) * 4 + 2}` },
        { participantId: `p${(gi + 1) * 4 + 3}` },
      ],
      matches: [],
    }));

    mockDb.query.tournamentGroups.findMany.mockResolvedValue([group, ...remainingGroups]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");

    const p3Result = results.find((r) => r.participantId === "p3");
    expect(p3Result).toBeDefined();
    // p3 lost all 3 group games (0 pts) — always last in group, never advances
    // → probFirst = probSecond = probThird = probFourth = 0
    expect(p3Result?.probabilities.probFirst).toBe(0);
    expect(p3Result?.probabilities.probSecond).toBe(0);
    expect(p3Result?.probabilities.probThird).toBe(0);
    expect(p3Result?.probabilities.probFourth).toBe(0);
  });

  it("probFifth through probEighth are equal for each participant (QF losers split evenly)", async () => {
    const participants = makeParticipants(48);
    mockDb.query.participants.findMany.mockResolvedValue(participants);
    mockDb.query.scoringEvents.findFirst.mockResolvedValue(null);
    mockDb.query.tournamentGroups.findMany.mockResolvedValue([]);
    mockDb.query.playoffMatches.findMany.mockResolvedValue([]);

    const sim = new WorldCupSimulator(500);
    const results = await sim.simulate("season-1");

    for (const r of results) {
      const { probFifth, probSixth, probSeventh, probEighth } = r.probabilities;
      expect(probFifth).toBeCloseTo(probSixth,   10);
      expect(probSixth).toBeCloseTo(probSeventh,  10);
      expect(probSeventh).toBeCloseTo(probEighth, 10);
    }
  });
});
