import { describe, it, expect } from "vitest";
import { buildFeederMap, groupMatchesByRound, computeEliminatedByRound } from "../PlayoffBracket";

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

function makeMatch(
  round: string,
  matchNumber: number,
  overrides: Partial<{
    participant1Id: string | null;
    participant2Id: string | null;
    winnerId: string | null;
    loserId: string | null;
    isComplete: boolean;
  }> = {}
) {
  return {
    id: `${round}-${matchNumber}`,
    round,
    matchNumber,
    participant1Id: overrides.participant1Id ?? `p${matchNumber * 2 - 1}`,
    participant2Id: overrides.participant2Id ?? `p${matchNumber * 2}`,
    winnerId: overrides.winnerId ?? null,
    loserId: overrides.loserId ?? null,
    isComplete: overrides.isComplete ?? false,
    participant1Score: null,
    participant2Score: null,
  };
}

// ---------------------------------------------------------------------------
// groupMatchesByRound
// ---------------------------------------------------------------------------

describe("groupMatchesByRound", () => {
  it("groups matches by round and sorts by matchNumber", () => {
    const matches = [
      makeMatch("Semifinals", 2),
      makeMatch("Semifinals", 1),
      makeMatch("Finals", 1),
    ];
    const grouped = groupMatchesByRound(matches);
    expect(grouped.get("Semifinals")?.map((m) => m.matchNumber)).toEqual([1, 2]);
    expect(grouped.get("Finals")?.map((m) => m.matchNumber)).toEqual([1]);
  });

  it("returns an empty map for an empty input", () => {
    expect(groupMatchesByRound([]).size).toBe(0);
  });
});

// ---------------------------------------------------------------------------
// buildFeederMap
// ---------------------------------------------------------------------------

describe("buildFeederMap", () => {
  it("returns an empty map when there is only one round", () => {
    const matches = [makeMatch("Finals", 1)];
    const map = buildFeederMap(groupMatchesByRound(matches), ["Finals"]);
    expect(map.size).toBe(0);
  });

  it("maps SF slots to the correct QF matches for an 8-team bracket", () => {
    const rounds = ["Quarterfinals", "Semifinals", "Finals"];
    const matches = [
      makeMatch("Quarterfinals", 1),
      makeMatch("Quarterfinals", 2),
      makeMatch("Quarterfinals", 3),
      makeMatch("Quarterfinals", 4),
      makeMatch("Semifinals", 1),
      makeMatch("Semifinals", 2),
      makeMatch("Finals", 1),
    ];

    const map = buildFeederMap(groupMatchesByRound(matches), rounds);

    // SF Match 1, slot p1 ← QF Match 1
    expect(map.get("Semifinals:1:p1")).toEqual({ round: "Quarterfinals", matchNumber: 1 });
    // SF Match 1, slot p2 ← QF Match 2
    expect(map.get("Semifinals:1:p2")).toEqual({ round: "Quarterfinals", matchNumber: 2 });
    // SF Match 2, slot p1 ← QF Match 3
    expect(map.get("Semifinals:2:p1")).toEqual({ round: "Quarterfinals", matchNumber: 3 });
    // SF Match 2, slot p2 ← QF Match 4
    expect(map.get("Semifinals:2:p2")).toEqual({ round: "Quarterfinals", matchNumber: 4 });
  });

  it("maps Finals slots to the correct SF matches", () => {
    const rounds = ["Quarterfinals", "Semifinals", "Finals"];
    const matches = [
      makeMatch("Quarterfinals", 1),
      makeMatch("Quarterfinals", 2),
      makeMatch("Quarterfinals", 3),
      makeMatch("Quarterfinals", 4),
      makeMatch("Semifinals", 1),
      makeMatch("Semifinals", 2),
      makeMatch("Finals", 1),
    ];

    const map = buildFeederMap(groupMatchesByRound(matches), rounds);

    expect(map.get("Finals:1:p1")).toEqual({ round: "Semifinals", matchNumber: 1 });
    expect(map.get("Finals:1:p2")).toEqual({ round: "Semifinals", matchNumber: 2 });
  });

  it("does not add an entry when the source match does not exist in the previous round", () => {
    const rounds = ["Quarterfinals", "Finals"];
    const matches = [
      makeMatch("Quarterfinals", 1),
      makeMatch("Quarterfinals", 2),
      makeMatch("Finals", 1),
    ];

    const map = buildFeederMap(groupMatchesByRound(matches), rounds);

    expect(map.get("Finals:1:p1")).toEqual({ round: "Quarterfinals", matchNumber: 1 });
    expect(map.get("Finals:1:p2")).toEqual({ round: "Quarterfinals", matchNumber: 2 });
    expect(map.has("Finals:2:p1")).toBe(false);
  });

  it("handles a 16-team bracket correctly for Round of 16 → Quarterfinals", () => {
    const rounds = ["Round of 16", "Quarterfinals", "Semifinals", "Finals"];
    const matches = [
      ...[1, 2, 3, 4, 5, 6, 7, 8].map((n) => makeMatch("Round of 16", n)),
      ...[1, 2, 3, 4].map((n) => makeMatch("Quarterfinals", n)),
      ...[1, 2].map((n) => makeMatch("Semifinals", n)),
      makeMatch("Finals", 1),
    ];

    const map = buildFeederMap(groupMatchesByRound(matches), rounds);

    expect(map.get("Quarterfinals:1:p1")).toEqual({ round: "Round of 16", matchNumber: 1 });
    expect(map.get("Quarterfinals:1:p2")).toEqual({ round: "Round of 16", matchNumber: 2 });
    expect(map.get("Quarterfinals:4:p1")).toEqual({ round: "Round of 16", matchNumber: 7 });
    expect(map.get("Quarterfinals:4:p2")).toEqual({ round: "Round of 16", matchNumber: 8 });
  });
});

// ---------------------------------------------------------------------------
// computeEliminatedByRound — double-chance bracket logic
// ---------------------------------------------------------------------------

type TestMatch = Parameters<typeof computeEliminatedByRound>[0][number];

function makeCompleteMatch(
  round: string,
  winnerId: string,
  loserId: string
): TestMatch {
  return {
    round,
    isComplete: true,
    winnerId,
    loser: { id: loserId, name: loserId },
  };
}

describe("computeEliminatedByRound", () => {
  describe("standard single-elimination (no double-chance)", () => {
    it("a R16 loser who never wins again IS shown as eliminated at R16", () => {
      const rounds = ["Round of 16", "Quarterfinals", "Semifinals", "Finals"];
      const matches: TestMatch[] = [
        // teamA wins R16; teamB loses R16 and has no further matches
        makeCompleteMatch("Round of 16", "teamA", "teamB"),
        makeCompleteMatch("Quarterfinals", "teamC", "teamA"), // teamA loses QF
      ];

      const result = computeEliminatedByRound(matches, rounds);

      // teamB is eliminated at R16 (never won a later round)
      expect(result.get("Round of 16")).toContain("teamB");
      // teamA is eliminated at QF (won R16, but lost QF)
      expect(result.get("Quarterfinals")).toContain("teamA");
      expect(result.get("Round of 16")).not.toContain("teamA");
    });
  });

  describe("AFL double-chance bracket", () => {
    // AFL afl_10 round order: Qualifying Finals → Elimination Finals →
    //   Semi-Finals → Preliminary Finals → Grand Final
    const AFL_ROUNDS = [
      "Qualifying Finals",
      "Elimination Finals",
      "Semi-Finals",
      "Preliminary Finals",
      "Grand Final",
    ];

    it("QF loser who wins a later Semi-Final is NOT shown as eliminated at QF", () => {
      const matches: TestMatch[] = [
        // teamA loses Qualifying Finals (double-chance — they come back via Semi-Finals)
        makeCompleteMatch("Qualifying Finals", "teamB", "teamA"),
        // teamA wins Semi-Finals comeback match
        makeCompleteMatch("Semi-Finals", "teamA", "teamC"),
      ];

      const result = computeEliminatedByRound(matches, AFL_ROUNDS);

      // teamA should NOT appear as eliminated at Qualifying Finals
      expect(result.get("Qualifying Finals") ?? []).not.toContain("teamA");
      // teamC (who lost the Semi-Final to teamA) IS eliminated
      expect(result.get("Semi-Finals")).toContain("teamC");
    });

    it("QF loser who LATER loses their Semi-Final comeback IS shown as eliminated at Semi-Finals", () => {
      const matches: TestMatch[] = [
        makeCompleteMatch("Qualifying Finals", "teamB", "teamA"),
        // teamA comes back via Semi-Finals but loses
        makeCompleteMatch("Semi-Finals", "teamC", "teamA"),
      ];

      const result = computeEliminatedByRound(matches, AFL_ROUNDS);

      // teamA finally eliminated at Semi-Finals, not at Qualifying Finals
      expect(result.get("Qualifying Finals") ?? []).not.toContain("teamA");
      expect(result.get("Semi-Finals")).toContain("teamA");
    });

    it("winning an EARLIER round (normal advancement) does not protect a later loser", () => {
      // Standard case: teamA wins R1, then loses R2 — they ARE eliminated at R2
      const rounds = ["Round of 16", "Quarterfinals", "Semifinals"];
      const matches: TestMatch[] = [
        makeCompleteMatch("Round of 16", "teamA", "teamZ"),
        makeCompleteMatch("Quarterfinals", "teamB", "teamA"),
      ];

      const result = computeEliminatedByRound(matches, rounds);

      // teamA's R16 win is normal advancement, not double-chance
      expect(result.get("Quarterfinals")).toContain("teamA");
      // teamA should not appear in R16 eliminated list
      expect(result.get("Round of 16") ?? []).not.toContain("teamA");
    });
  });
});
