import { describe, it, expect } from 'vitest';
import {
  calculateICM,
  calculateICMFromOdds,
  icmResultToArray,
  type ParticipantChips,
} from '../icm-calculator';

describe('icm-calculator', () => {
  describe('calculateICM', () => {
    it('calculates probabilities for 8 equal participants', () => {
      const participants: ParticipantChips[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        championshipProbability: 0.125, // Equal 12.5% each
      }));

      const results = calculateICM(participants);

      expect(results.size).toBe(8);

      // Each participant should have probabilities that sum to 1.0
      results.forEach((result) => {
        const probs = icmResultToArray(result);
        const sum = probs.reduce((acc, p) => acc + p, 0);
        expect(sum).toBeCloseTo(1.0, 1);

        // With equal odds, probabilities vary by placement preference
        // But should all be reasonable (not 0, not 1)
        probs.forEach(p => {
          expect(p).toBeGreaterThan(0);
          expect(p).toBeLessThan(0.5);
        });
      });
    });

    it('gives stronger team higher probabilities for better placements', () => {
      const participants: ParticipantChips[] = [
        { participantId: 'strong', championshipProbability: 0.5 },  // 50%
        { participantId: 'weak', championshipProbability: 0.01 },   // 1%
        ...Array.from({ length: 6 }, (_, i) => ({
          participantId: String(i + 3),
          championshipProbability: 0.0817, // ~8.17% each
        })),
      ];

      const results = calculateICM(participants);

      const strongResult = results.get('strong');
      const weakResult = results.get('weak');
      if (!strongResult || !weakResult) throw new Error('Expected results not found');
      const strongProbs = icmResultToArray(strongResult);
      const weakProbs = icmResultToArray(weakResult);

      // Strong team should have higher P(1st) than weak team
      expect(strongProbs[0]).toBeGreaterThan(weakProbs[0]);

      // Strong team should have higher P(2nd) than weak team
      expect(strongProbs[1]).toBeGreaterThan(weakProbs[1]);

      // Weak team should have higher probability of worse placements
      expect(weakProbs[7]).toBeGreaterThan(strongProbs[7]);
    });

    it('handles 32 team NHL scenario', () => {
      // Simulate realistic NHL championship odds distribution
      const participants: ParticipantChips[] = [
        { participantId: 'COL', championshipProbability: 0.154 },  // 15.4% favorite
        { participantId: 'FLA', championshipProbability: 0.111 },  // 11.1%
        { participantId: 'VGK', championshipProbability: 0.111 },  // 11.1%
        { participantId: 'TBL', championshipProbability: 0.091 },  // 9.1%
        { participantId: 'NJD', championshipProbability: 0.067 },  // 6.7%
        { participantId: 'TOR', championshipProbability: 0.038 },  // 3.8%
        { participantId: 'NYR', championshipProbability: 0.024 },  // 2.4%
        { participantId: 'DET', championshipProbability: 0.013 },  // 1.3%
        // 24 more teams with decreasing odds
        ...Array.from({ length: 24 }, (_, i) => ({
          participantId: `TEAM${i + 9}`,
          championshipProbability: 0.013 / (i + 2), // Decreasing odds
        })),
      ];

      const results = calculateICM(participants);

      expect(results.size).toBe(32);

      // Colorado (favorite) should have highest P(1st)
      const colResult32 = results.get('COL');
      if (!colResult32) throw new Error('COL result not found');
      const colProbs = icmResultToArray(colResult32);
      expect(colProbs[0]).toBeGreaterThan(0.05); // Should have >5% chance of 1st

      // Even the worst team should have some probability for all placements
      const worstResult = results.get('TEAM32');
      if (!worstResult) throw new Error('TEAM32 result not found');
      const worstProbs = icmResultToArray(worstResult);
      worstProbs.forEach(p => {
        expect(p).toBeGreaterThan(0); // Not zero
        expect(p).toBeLessThan(1);    // Valid probability
      });

      // Column sums should equal 1.0 (each position distributed across all teams)
      for (let place = 0; place < 8; place++) {
        let colSum = 0;
        results.forEach((result) => {
          const probs = icmResultToArray(result);
          colSum += probs[place];
        });
        expect(colSum).toBeCloseTo(1.0, 2);
      }
    });

    it('handles edge case with single participant', () => {
      const participants: ParticipantChips[] = [
        { participantId: '1', championshipProbability: 1.0 },
      ];

      const results = calculateICM(participants);

      expect(results.size).toBe(1);

      const singleResult = results.get('1');
      if (!singleResult) throw new Error('Result for participant 1 not found');
      const probs = icmResultToArray(singleResult);
      // With 1 participant, only 1st place is filled (removed from pool after)
      expect(probs[0]).toBeCloseTo(1.0, 1); // 100% chance of 1st
      const sum = probs.reduce((acc, p) => acc + p, 0);
      expect(sum).toBeCloseTo(1.0, 1);
    });

    it('handles zero championship probabilities gracefully', () => {
      const participants: ParticipantChips[] = [
        { participantId: '1', championshipProbability: 0 },
        { participantId: '2', championshipProbability: 0 },
        { participantId: '3', championshipProbability: 0 },
      ];

      const results = calculateICM(participants);

      expect(results.size).toBe(3);

      // Should give equal probabilities when all have zero odds
      // With 3 teams, only positions 0-2 are filled (each team removed after placement)
      results.forEach((result) => {
        const probs = icmResultToArray(result);
        // First 3 positions should each have ~1/3
        for (let i = 0; i < 3; i++) {
          expect(probs[i]).toBeCloseTo(1/3, 1);
        }
        // Positions 3-7 are 0 (no participants left to fill them)
        for (let i = 3; i < 8; i++) {
          expect(probs[i]).toBe(0);
        }
      });
    });

    it('normalizes championship probabilities that do not sum to 1.0', () => {
      const participants: ParticipantChips[] = [
        { participantId: '1', championshipProbability: 0.6 },   // 60% (with vig)
        { participantId: '2', championshipProbability: 0.55 },  // 55% (with vig)
        // Total > 1.0, should be normalized
      ];

      const results = calculateICM(participants);

      expect(results.size).toBe(2);

      // With 2 participants, only positions 0-1 are filled
      // Verify those columns sum to 1.0
      for (let place = 0; place < 2; place++) {
        let colSum = 0;
        results.forEach((result) => {
          const probs = icmResultToArray(result);
          colSum += probs[place];
        });
        expect(colSum).toBeCloseTo(1.0, 2);
      }

      // Participant 1 (higher prob) should have higher P(1st)
      const r1 = results.get('1');
      const r2 = results.get('2');
      if (!r1 || !r2) throw new Error('Results for participants 1 and 2 not found');
      const probs1 = icmResultToArray(r1);
      const probs2 = icmResultToArray(r2);
      expect(probs1[0]).toBeGreaterThan(probs2[0]);
    });

    it('returns empty map for empty input', () => {
      const results = calculateICM([]);
      expect(results.size).toBe(0);
    });

    it('maintains probability ordering for sorted championship odds', () => {
      const participants: ParticipantChips[] = [
        { participantId: '1st', championshipProbability: 0.40 },
        { participantId: '2nd', championshipProbability: 0.30 },
        { participantId: '3rd', championshipProbability: 0.20 },
        { participantId: '4th', championshipProbability: 0.10 },
      ];

      const results = calculateICM(participants);

      const rFirst = results.get('1st');
      const rSecond = results.get('2nd');
      const rThird = results.get('3rd');
      const rFourth = results.get('4th');
      if (!rFirst || !rSecond || !rThird || !rFourth) throw new Error('Expected results not found');
      const first = icmResultToArray(rFirst);
      const second = icmResultToArray(rSecond);
      const third = icmResultToArray(rThird);
      const fourth = icmResultToArray(rFourth);

      // P(1st place) should be ordered
      expect(first[0]).toBeGreaterThan(second[0]);
      expect(second[0]).toBeGreaterThan(third[0]);
      expect(third[0]).toBeGreaterThan(fourth[0]);
    });
  });

  describe('calculateICMFromOdds', () => {
    it('converts American odds to ICM probabilities', () => {
      const odds = [
        { participantId: 'COL', odds: 550 },   // +550
        { participantId: 'FLA', odds: 800 },   // +800
        { participantId: 'ARI', odds: 100000 }, // +100000 (longshot)
      ];

      const results = calculateICMFromOdds(odds);

      expect(results.size).toBe(3);

      // Colorado should have better odds than Arizona
      const colResultOdds = results.get('COL');
      const ariResult = results.get('ARI');
      if (!colResultOdds || !ariResult) throw new Error('COL or ARI result not found');
      const colProbs = icmResultToArray(colResultOdds);
      const ariProbs = icmResultToArray(ariResult);

      expect(colProbs[0]).toBeGreaterThan(ariProbs[0]);

      // Even Arizona should have some probability
      expect(ariProbs[0]).toBeGreaterThan(0);
    });

    it('handles negative odds (favorites)', () => {
      const odds = [
        { participantId: 'FAV', odds: -200 },  // Favorite
        { participantId: 'DOG', odds: 500 },   // Underdog
      ];

      const results = calculateICMFromOdds(odds);

      const favResult = results.get('FAV');
      const dogResult = results.get('DOG');
      if (!favResult || !dogResult) throw new Error('FAV or DOG result not found');
      const favProbs = icmResultToArray(favResult);
      const dogProbs = icmResultToArray(dogResult);

      // Favorite should have higher P(1st)
      expect(favProbs[0]).toBeGreaterThan(dogProbs[0]);
    });

    it('uses custom scoring places', () => {
      const odds = [
        { participantId: '1', odds: 200 },
        { participantId: '2', odds: 300 },
        { participantId: '3', odds: 400 },
      ];

      const results = calculateICMFromOdds(odds, 5); // 5 scoring places

      results.forEach((result) => {
        const probs = icmResultToArray(result);
        // Should still have 8 values but calculated for 5 places
        expect(probs).toHaveLength(8);
      });
    });
  });

  describe('icmResultToArray', () => {
    it('converts ICM result to array format', () => {
      const icmResult = {
        participantId: 'TEST',
        probabilities: {
          first: 0.25,
          second: 0.20,
          third: 0.15,
          fourth: 0.12,
          fifth: 0.10,
          sixth: 0.08,
          seventh: 0.06,
          eighth: 0.04,
        },
      };

      const array = icmResultToArray(icmResult);

      expect(array).toEqual([0.25, 0.20, 0.15, 0.12, 0.10, 0.08, 0.06, 0.04]);
      expect(array).toHaveLength(8);
    });
  });

  describe('integration: realistic NHL 32-team scenario', () => {
    it('calculates reasonable probabilities for full NHL league', () => {
      // Full 32-team NHL with realistic odds distribution
      const odds = [
        { participantId: 'COL', odds: 550 },
        { participantId: 'FLA', odds: 800 },
        { participantId: 'VGK', odds: 800 },
        { participantId: 'TBL', odds: 1000 },
        { participantId: 'NJD', odds: 1400 },
        { participantId: 'TOR', odds: 2500 },
        { participantId: 'NYR', odds: 4000 },
        { participantId: 'DET', odds: 7500 },
        { participantId: 'VAN', odds: 7500 },
        { participantId: 'NYI', odds: 15000 },
        { participantId: 'NSH', odds: 40000 },
        // 21 more teams with increasing odds
        ...Array.from({ length: 21 }, (_, i) => ({
          participantId: `TEAM${i + 12}`,
          odds: 40000 + (i + 1) * 5000, // Increasing odds
        })),
      ];

      const results = calculateICMFromOdds(odds);

      expect(results.size).toBe(32);

      // Favorite (Colorado) should have reasonable championship probability
      const colResultInteg = results.get('COL');
      const detResult = results.get('DET');
      const longResult = results.get('TEAM32');
      if (!colResultInteg || !detResult || !longResult) throw new Error('Expected results not found');
      const colProbs = icmResultToArray(colResultInteg);
      expect(colProbs[0]).toBeGreaterThan(0.05); // >5% for 1st
      expect(colProbs[0]).toBeLessThan(0.35);     // <35% for 1st (not guaranteed)

      // Middle team (Detroit) should have middling probabilities
      const detProbs = icmResultToArray(detResult);
      expect(detProbs[0]).toBeGreaterThan(0);     // Some chance
      expect(detProbs[0]).toBeLessThan(0.10);     // But not high

      // Longshot should have very small but non-zero probabilities
      const longProbs = icmResultToArray(longResult);
      expect(longProbs[0]).toBeGreaterThan(0);    // Not impossible
      expect(longProbs[0]).toBeLessThan(0.05);    // But unlikely (with 32 teams, even worst has ~3% uniform)

      // Column sums should equal 1.0 (each position distributed across all teams)
      for (let place = 0; place < 8; place++) {
        let colSum = 0;
        results.forEach((result) => {
          const probs = icmResultToArray(result);
          colSum += probs[place];
        });
        expect(colSum).toBeCloseTo(1.0, 2);
      }
    });
  });
});
