import { describe, it, expect } from "vitest";

/**
 * Tiebreaker Logic Tests
 * Phase 4.1: Test comprehensive tiebreaker scenarios
 *
 * Tiebreaker order:
 * 1. Total points (higher is better)
 * 2. Number of 1st place finishes
 * 3. Number of 2nd place finishes
 * 4. Continue through all placements (3rd, 4th, ..., 8th)
 *
 * Note: These are unit tests that test the tiebreaker logic in isolation.
 * The actual recalculateStandings function is tested through integration tests
 * in actual league scenarios.
 */

/**
 * Helper function to simulate compareTeamsForRanking logic
 * This mirrors the private function in scoring-calculator.ts
 */
function compareTeamsForRanking(
  teamA: {
    totalPoints: number;
    placementCounts: Record<number, number>;
  },
  teamB: {
    totalPoints: number;
    placementCounts: Record<number, number>;
  }
): number {
  // First compare by total points (descending)
  if (teamA.totalPoints !== teamB.totalPoints) {
    return teamB.totalPoints - teamA.totalPoints;
  }

  // If points are tied, apply tiebreaker cascade
  for (let placement = 1; placement <= 8; placement++) {
    const countA = teamA.placementCounts[placement] || 0;
    const countB = teamB.placementCounts[placement] || 0;

    if (countA !== countB) {
      return countB - countA; // More of this placement is better
    }
  }

  // Completely tied
  return 0;
}

/**
 * Helper function to simulate assignRanks logic
 * This mirrors the private function in scoring-calculator.ts
 */
function assignRanks(
  teams: Array<{
    teamId: string;
    totalPoints: number;
    placementCounts: Record<number, number>;
  }>
): Map<string, number> {
  // Sort teams by ranking criteria
  const sorted = [...teams].toSorted(compareTeamsForRanking);

  const ranks = new Map<string, number>();
  let currentRank = 1;

  for (let i = 0; i < sorted.length; i++) {
    const team = sorted[i];

    // Check if this team is tied with the previous team
    if (i > 0) {
      const prevTeam = sorted[i - 1];
      const comparison = compareTeamsForRanking(team, prevTeam);

      if (comparison !== 0) {
        // Not tied, advance rank
        currentRank = i + 1;
      }
      // If comparison === 0, keep the same rank (tie)
    }

    ranks.set(team.teamId, currentRank);
  }

  return ranks;
}

describe("Tiebreaker Logic", () => {

  describe("Basic Point Sorting", () => {
    it("should rank teams by total points descending", () => {
      const teams = [
        { teamId: "team-a", totalPoints: 100, placementCounts: {} },
        { teamId: "team-b", totalPoints: 200, placementCounts: {} },
        { teamId: "team-c", totalPoints: 150, placementCounts: {} },
      ];

      const ranks = assignRanks(teams);

      expect(ranks.get("team-b")).toBe(1); // Highest points
      expect(ranks.get("team-c")).toBe(2);
      expect(ranks.get("team-a")).toBe(3); // Lowest points
    });
  });

  describe("First Place Tiebreaker", () => {
    it("should break tie using 1st place count", () => {
      // Both teams have 215 points total, different 1st place counts
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 215,
          placementCounts: { 1: 2, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 1 }, // 2 firsts
        },
        {
          teamId: "team-b",
          totalPoints: 215,
          placementCounts: { 1: 1, 2: 1, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 1 }, // 1 first
        },
      ];

      const ranks = assignRanks(teams);

      // Team A should rank higher due to more 1st place finishes
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(2);
    });

    it("should handle multiple teams with same points but different 1st place counts", () => {
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 215,
          placementCounts: { 1: 2, 8: 1 } as Record<number, number>, // 2 firsts
        },
        {
          teamId: "team-b",
          totalPoints: 215,
          placementCounts: { 1: 1, 2: 1, 8: 1 } as Record<number, number>, // 1 first
        },
        {
          teamId: "team-c",
          totalPoints: 215,
          placementCounts: { 2: 2, 3: 1 } as Record<number, number>, // 0 firsts
        },
      ];

      const ranks = assignRanks(teams);

      // Ranked by 1st place count
      expect(ranks.get("team-a")).toBe(1); // 2 firsts
      expect(ranks.get("team-b")).toBe(2); // 1 first
      expect(ranks.get("team-c")).toBe(3); // 0 firsts
    });
  });

  describe("Second Place Tiebreaker", () => {
    it("should break tie using 2nd place count when 1st place count is tied", () => {
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 240,
          placementCounts: { 1: 1, 2: 2, 8: 1 } as Record<number, number>, // 1 first, 2 seconds
        },
        {
          teamId: "team-b",
          totalPoints: 240,
          placementCounts: { 1: 1, 3: 2, 8: 1 } as Record<number, number>, // 1 first, 0 seconds
        },
      ];

      const ranks = assignRanks(teams);

      expect(ranks.get("team-a")).toBe(1); // More 2nd places
      expect(ranks.get("team-b")).toBe(2);
    });
  });

  describe("Cascading Tiebreakers", () => {
    it("should cascade through all placement counts when needed", () => {
      // Tiebreaker goes deep: same points, same 1st-3rd, different 4th
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 160,
          placementCounts: { 2: 1, 3: 1, 4: 1 } as Record<number, number>, // 0×1st, 1×2nd, 1×3rd, 1×4th
        },
        {
          teamId: "team-b",
          totalPoints: 160,
          placementCounts: { 2: 1, 3: 1, 5: 2 } as Record<number, number>, // 0×1st, 1×2nd, 1×3rd, 0×4th, 2×5th
        },
      ];

      const ranks = assignRanks(teams);

      // Team A wins on 4th place count
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(2);
    });
  });

  describe("Complete Ties", () => {
    it("should assign same rank to teams with identical scores and placements", () => {
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 220,
          placementCounts: { 1: 1, 2: 1, 3: 1 },
        },
        {
          teamId: "team-b",
          totalPoints: 220,
          placementCounts: { 1: 1, 2: 1, 3: 1 },
        },
      ];

      const ranks = assignRanks(teams);

      // Both should have rank 1
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(1);
    });

    it("should handle 3-way ties correctly", () => {
      const teams = [
        { teamId: "team-a", totalPoints: 170, placementCounts: { 1: 1, 2: 1 } },
        { teamId: "team-b", totalPoints: 170, placementCounts: { 1: 1, 2: 1 } },
        { teamId: "team-c", totalPoints: 170, placementCounts: { 1: 1, 2: 1 } },
      ];

      const ranks = assignRanks(teams);

      // All three should have rank 1
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(1);
      expect(ranks.get("team-c")).toBe(1);
    });

    it("should skip rank numbers after ties", () => {
      // 2 teams tied for 1st, next team should be 3rd
      const teams = [
        { teamId: "team-a", totalPoints: 300, placementCounts: { 1: 3 } as Record<number, number> },
        { teamId: "team-b", totalPoints: 300, placementCounts: { 1: 3 } as Record<number, number> }, // Tied
        { teamId: "team-c", totalPoints: 210, placementCounts: { 2: 3 } as Record<number, number> },
      ];

      const ranks = assignRanks(teams);

      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(1);
      expect(ranks.get("team-c")).toBe(3); // Skips 2
    });
  });

  describe("Edge Cases", () => {
    it("should handle season with single team", () => {
      const teams = [{ teamId: "solo", totalPoints: 100, placementCounts: { 1: 1 } }];

      const ranks = assignRanks(teams);

      expect(ranks.size).toBe(1);
      expect(ranks.get("solo")).toBe(1);
    });

    it("should handle teams with zero points", () => {
      const teams = [
        { teamId: "team-a", totalPoints: 100, placementCounts: { 1: 1 } as Record<number, number> },
        { teamId: "team-b", totalPoints: 0, placementCounts: {} as Record<number, number> },
      ];

      const ranks = assignRanks(teams);

      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(2);
    });

    it("should handle all teams with same score", () => {
      const teams = [
        { teamId: "team-a", totalPoints: 100, placementCounts: { 1: 1 } },
        { teamId: "team-b", totalPoints: 100, placementCounts: { 1: 1 } },
        { teamId: "team-c", totalPoints: 100, placementCounts: { 1: 1 } },
      ];

      const ranks = assignRanks(teams);

      // All should be rank 1
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(1);
      expect(ranks.get("team-c")).toBe(1);
    });
  });

  describe("Complex Tiebreaker Scenarios", () => {
    it("should handle 8-way cascade ending in complete tie", () => {
      // Two teams identical through all 8 placements
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 450,
          placementCounts: { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1 },
        },
        {
          teamId: "team-b",
          totalPoints: 450,
          placementCounts: { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1 },
        },
      ];

      const ranks = assignRanks(teams);

      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(1); // Perfect tie
    });

    it("should break tie at 8th place (last tiebreaker)", () => {
      // Identical through 1st-7th, different at 8th
      const teams = [
        {
          teamId: "team-a",
          totalPoints: 100,
          placementCounts: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 7 }, // 7×8th
        },
        {
          teamId: "team-b",
          totalPoints: 100,
          placementCounts: { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 6 }, // 6×8th
        },
      ];

      const ranks = assignRanks(teams);

      // Team A wins with more 8th place finishes
      expect(ranks.get("team-a")).toBe(1);
      expect(ranks.get("team-b")).toBe(2);
    });
  });
});
