import { describe, expect, it } from "vitest";
import {
  computeGroupStandings,
  generateRoundRobinPairings,
} from "../group-stage-match";

const TEAMS = [
  { participantId: "t1", participantName: "Brazil" },
  { participantId: "t2", participantName: "France" },
  { participantId: "t3", participantName: "Germany" },
  { participantId: "t4", participantName: "Argentina" },
];

function match(
  p1: string,
  p2: string,
  s1: number,
  s2: number,
  isComplete = true
) {
  return {
    participant1Id: p1,
    participant2Id: p2,
    participant1Score: s1,
    participant2Score: s2,
    isComplete,
  };
}

function findTeam(
  standings: ReturnType<typeof computeGroupStandings>,
  id: string
) {
  const row = standings.find((s) => s.participantId === id);
  if (!row) throw new Error(`Team ${id} not found in standings`);
  return row;
}

describe("computeGroupStandings", () => {
  it("returns all four teams with zeroes when no matches are complete", () => {
    const standings = computeGroupStandings(TEAMS, [
      {
        participant1Id: "t1",
        participant2Id: "t2",
        participant1Score: null,
        participant2Score: null,
        isComplete: false,
      },
    ]);
    expect(standings).toHaveLength(4);
    for (const row of standings) {
      expect(row.played).toBe(0);
      expect(row.points).toBe(0);
    }
  });

  it("awards 3 points for a win and 0 for a loss", () => {
    const standings = computeGroupStandings(TEAMS, [match("t1", "t2", 2, 0)]);
    const brazil = findTeam(standings, "t1");
    const france = findTeam(standings, "t2");
    expect(brazil.points).toBe(3);
    expect(brazil.wins).toBe(1);
    expect(france.points).toBe(0);
    expect(france.losses).toBe(1);
  });

  it("awards 1 point each for a draw", () => {
    const standings = computeGroupStandings(TEAMS, [match("t1", "t2", 1, 1)]);
    const brazil = findTeam(standings, "t1");
    const france = findTeam(standings, "t2");
    expect(brazil.points).toBe(1);
    expect(brazil.draws).toBe(1);
    expect(france.points).toBe(1);
    expect(france.draws).toBe(1);
  });

  it("tracks goals for, against, and difference correctly", () => {
    const standings = computeGroupStandings(TEAMS, [match("t1", "t2", 3, 1)]);
    const brazil = findTeam(standings, "t1");
    const france = findTeam(standings, "t2");
    expect(brazil.gf).toBe(3);
    expect(brazil.ga).toBe(1);
    expect(brazil.gd).toBe(2);
    expect(france.gf).toBe(1);
    expect(france.ga).toBe(3);
    expect(france.gd).toBe(-2);
  });

  it("sorts by points descending", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t2", 2, 0), // Brazil 3pts
      match("t3", "t4", 1, 0), // Germany 3pts
      match("t1", "t3", 0, 0), // Brazil 1pt, Germany 1pt
      match("t2", "t4", 2, 0), // France 3pts
    ]);
    const ids = standings.map((s) => s.participantId);
    // Brazil: 4pts gd+2, Germany: 4pts gd-1, France: 3pts, Argentina: 0pts
    expect(ids[0]).toBe("t1"); // Brazil
    expect(ids[1]).toBe("t3"); // Germany
    expect(ids[2]).toBe("t2"); // France
    expect(ids[3]).toBe("t4"); // Argentina
  });

  it("uses goal difference as tiebreaker after points", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t3", 3, 0), // Brazil big win
      match("t2", "t4", 1, 0), // France narrow win
    ]);
    expect(standings[0].participantId).toBe("t1"); // Brazil: 3pts, gd+3
    expect(standings[1].participantId).toBe("t2"); // France: 3pts, gd+1
  });

  it("uses goals for as tiebreaker when points and gd are equal", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t3", 2, 1), // Brazil: 3pts, gd+1, gf2
      match("t2", "t4", 3, 2), // France: 3pts, gd+1, gf3
    ]);
    expect(standings[0].participantId).toBe("t2"); // France higher gf
    expect(standings[1].participantId).toBe("t1");
  });

  it("falls back to alphabetical name when fully tied", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t3", 1, 0),
      match("t2", "t4", 1, 0),
    ]);
    // Both t1 (Brazil) and t2 (France) have 3pts, gd+1, gf1
    expect(standings[0].participantId).toBe("t1"); // Brazil < France alphabetically
    expect(standings[1].participantId).toBe("t2");
  });

  it("handles a full 6-match group correctly", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t4", 3, 0), // Brazil beat Argentina
      match("t2", "t3", 2, 1), // France beat Germany
      match("t1", "t3", 2, 0), // Brazil beat Germany
      match("t4", "t2", 0, 1), // France beat Argentina
      match("t1", "t2", 1, 0), // Brazil beat France
      match("t3", "t4", 2, 1), // Germany beat Argentina
    ]);
    expect(standings[0].participantId).toBe("t1"); // Brazil 9pts
    expect(standings[0].points).toBe(9);
    expect(standings[1].participantId).toBe("t2"); // France 6pts
    expect(standings[2].participantId).toBe("t3"); // Germany 3pts
    expect(standings[3].participantId).toBe("t4"); // Argentina 0pts
  });

  it("skips incomplete matches", () => {
    const standings = computeGroupStandings(TEAMS, [
      match("t1", "t2", 2, 0, true),
      match("t3", "t4", 1, 0, false),
    ]);
    expect(findTeam(standings, "t1").played).toBe(1);
    expect(findTeam(standings, "t3").played).toBe(0);
  });
});

describe("generateRoundRobinPairings", () => {
  it("generates exactly 6 pairings for 4 teams", () => {
    const pairings = generateRoundRobinPairings(["t1", "t2", "t3", "t4"]);
    expect(pairings).toHaveLength(6);
  });

  it("covers all 6 unique matchups", () => {
    const pairings = generateRoundRobinPairings(["t1", "t2", "t3", "t4"]);
    const keys = new Set(
      pairings.map((p) =>
        [p.participant1Id, p.participant2Id].toSorted().join("-")
      )
    );
    expect(keys.size).toBe(6);
  });

  it("uses matchdays 1, 2, and 3", () => {
    const pairings = generateRoundRobinPairings(["t1", "t2", "t3", "t4"]);
    const matchdays = [...new Set(pairings.map((p) => p.matchday))].toSorted();
    expect(matchdays).toEqual([1, 2, 3]);
  });

  it("has exactly 2 matches per matchday", () => {
    const pairings = generateRoundRobinPairings(["t1", "t2", "t3", "t4"]);
    for (const day of [1, 2, 3]) {
      expect(pairings.filter((p) => p.matchday === day)).toHaveLength(2);
    }
  });

  it("throws if not exactly 4 participants", () => {
    expect(() => generateRoundRobinPairings(["t1", "t2", "t3"])).toThrow();
  });
});
