import { describe, it, expect } from 'vitest';
import {
  simulateBracket,
  simulateBracketSync,
  getExpectedCounts,
  type TeamForSimulation,
  type ProbabilityDistribution,
} from '../bracket-simulator';

describe('bracket-simulator', () => {
  describe('simulateBracketSync', () => {
    it('simulates 8-team bracket with equal ratings', () => {
      const teams: TeamForSimulation[] = [
        { participantId: '1', elo: 1500 },
        { participantId: '2', elo: 1500 },
        { participantId: '3', elo: 1500 },
        { participantId: '4', elo: 1500 },
        { participantId: '5', elo: 1500 },
        { participantId: '6', elo: 1500 },
        { participantId: '7', elo: 1500 },
        { participantId: '8', elo: 1500 },
      ];

      const results = simulateBracketSync(teams, 'nhl-8', 10000);

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

      // With equal ratings, each team should have roughly equal probability
      results.forEach((distribution, _participantId) => {
        // Sum of probabilities should be 1.0
        const sum = distribution.reduce((acc, p) => acc + p, 0);
        expect(sum).toBeCloseTo(1.0, 1);

        // Each team should win championship ~12.5% of the time (1/8)
        expect(distribution[0]).toBeGreaterThan(0.08);
        expect(distribution[0]).toBeLessThan(0.17);
      });
    });

    it('gives higher win probability to stronger team', () => {
      const teams: TeamForSimulation[] = [
        { participantId: 'strong', elo: 1700 },  // Much stronger
        { participantId: '2', elo: 1500 },
        { participantId: '3', elo: 1500 },
        { participantId: '4', elo: 1500 },
        { participantId: '5', elo: 1500 },
        { participantId: '6', elo: 1500 },
        { participantId: '7', elo: 1500 },
        { participantId: 'weak', elo: 1300 },    // Much weaker
      ];

      const results = simulateBracketSync(teams, 'nhl-8', 10000);

      const strongDist = results.get('strong');
      const weakDist = results.get('weak');
      if (!strongDist || !weakDist) throw new Error('strong or weak result not found');

      // Strong team should have higher championship probability
      expect(strongDist[0]).toBeGreaterThan(weakDist[0]);
      expect(strongDist[0]).toBeGreaterThan(0.2); // Should win >20% of the time

      // Weak team should have lower championship probability
      expect(weakDist[0]).toBeLessThan(0.1); // Should win <10% of the time
    });

    it('handles extreme Elo differences', () => {
      const teams: TeamForSimulation[] = [
        { participantId: 'champion', elo: 1900 },  // Dominant
        { participantId: '2', elo: 1400 },
        { participantId: '3', elo: 1400 },
        { participantId: '4', elo: 1400 },
        { participantId: '5', elo: 1400 },
        { participantId: '6', elo: 1400 },
        { participantId: '7', elo: 1400 },
        { participantId: '8', elo: 1400 },
      ];

      const results = simulateBracketSync(teams, 'nhl-8', 10000);

      const championDist = results.get('champion');
      if (!championDist) throw new Error('champion result not found');

      // Dominant team should win very often
      expect(championDist[0]).toBeGreaterThan(0.6); // >60% championship probability
    });

    it('ensures probabilities sum to 1.0 for each team', () => {
      const teams: TeamForSimulation[] = [
        { participantId: '1', elo: 1650 },
        { participantId: '2', elo: 1600 },
        { participantId: '3', elo: 1550 },
        { participantId: '4', elo: 1500 },
        { participantId: '5', elo: 1450 },
        { participantId: '6', elo: 1400 },
        { participantId: '7', elo: 1350 },
        { participantId: '8', elo: 1300 },
      ];

      const results = simulateBracketSync(teams, 'nhl-8', 5000);

      results.forEach((distribution, _participantId) => {
        const sum = distribution.reduce((acc, p) => acc + p, 0);
        expect(sum).toBeCloseTo(1.0, 1);
      });
    });

    it('throws error for wrong number of teams', () => {
      const teams: TeamForSimulation[] = [
        { participantId: '1', elo: 1500 },
        { participantId: '2', elo: 1500 },
      ];

      expect(() => {
        simulateBracketSync(teams, 'nhl-8', 1000);
      }).toThrow('requires exactly 8 teams');
    });

    it('throws error for invalid simulation count', () => {
      const teams: TeamForSimulation[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        elo: 1500,
      }));

      expect(() => {
        simulateBracketSync(teams, 'nhl-8', 0);
      }).toThrow('must be positive');

      expect(() => {
        simulateBracketSync(teams, 'nhl-8', -100);
      }).toThrow('must be positive');
    });

    it('produces consistent results with same random seed', () => {
      const teams: TeamForSimulation[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        elo: 1500 + i * 50,
      }));

      // Run with small sample size for speed
      const results1 = simulateBracketSync(teams, 'nhl-8', 1000);
      const results2 = simulateBracketSync(teams, 'nhl-8', 1000);

      // Results won't be identical due to randomness, but should be in same ballpark
      const dist1 = results1.get('1');
      const dist2 = results2.get('1');
      if (!dist1 || !dist2) throw new Error('Result for participant 1 not found');

      // Championship probabilities should be within 10% of each other
      expect(Math.abs(dist1[0] - dist2[0])).toBeLessThan(0.1);
    });

    it('returns valid probability distribution structure', () => {
      const teams: TeamForSimulation[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        elo: 1500,
      }));

      const results = simulateBracketSync(teams, 'nhl-8', 1000);

      results.forEach((distribution, _participantId) => {
        // Should be array of 8 numbers
        expect(distribution).toHaveLength(8);

        // All probabilities should be between 0 and 1
        distribution.forEach(prob => {
          expect(prob).toBeGreaterThanOrEqual(0);
          expect(prob).toBeLessThanOrEqual(1);
        });
      });
    });
  });

  describe('simulateBracket (async)', () => {
    it('simulates bracket asynchronously', async () => {
      const teams: TeamForSimulation[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        elo: 1500,
      }));

      const results = await simulateBracket(teams, 'nhl-8', 5000);

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

      results.forEach((distribution) => {
        const sum = distribution.reduce((acc, p) => acc + p, 0);
        expect(sum).toBeCloseTo(1.0, 1);
      });
    });

    it('calls progress callback', async () => {
      const teams: TeamForSimulation[] = Array.from({ length: 8 }, (_, i) => ({
        participantId: String(i + 1),
        elo: 1500,
      }));

      const progressCalls: Array<{ current: number; total: number }> = [];

      await simulateBracket(teams, 'nhl-8', 25000, (current, total) => {
        progressCalls.push({ current, total });
      });

      // Should have progress updates at 10k, 20k, and 25k
      expect(progressCalls.length).toBeGreaterThan(0);
      expect(progressCalls[progressCalls.length - 1].current).toBe(25000);
      expect(progressCalls[progressCalls.length - 1].total).toBe(25000);
    });
  });

  describe('getExpectedCounts', () => {
    it('converts probabilities to expected counts', () => {
      const distribution: ProbabilityDistribution = [
        0.25, // 25% 1st
        0.20, // 20% 2nd
        0.15, // 15% 3rd
        0.10, // 10% 4th
        0.10, // 10% 5th
        0.10, // 10% 6th
        0.05, // 5% 7th
        0.05, // 5% 8th
      ];

      const counts = getExpectedCounts(distribution, 10000);

      expect(counts[1]).toBe(2500);
      expect(counts[2]).toBe(2000);
      expect(counts[3]).toBe(1500);
      expect(counts[4]).toBe(1000);
      expect(counts[5]).toBe(1000);
      expect(counts[6]).toBe(1000);
      expect(counts[7]).toBe(500);
      expect(counts[8]).toBe(500);
    });

    it('rounds to nearest integer', () => {
      const distribution: ProbabilityDistribution = [
        0.123, 0.123, 0.123, 0.123, 0.123, 0.123, 0.131, 0.131,
      ];

      const counts = getExpectedCounts(distribution, 1000);

      // Should round 123 and 131
      expect(counts[1]).toBe(123);
      expect(counts[7]).toBe(131);
    });
  });

  describe('integration: realistic NHL scenario', () => {
    it('simulates NHL playoff bracket with realistic Elo ratings', async () => {
      // Based on plan: Colorado (1648), NY Islanders (1324), etc.
      const teams: TeamForSimulation[] = [
        { participantId: 'COL', elo: 1648 },  // Colorado (strongest)
        { participantId: 'FLA', elo: 1620 },  // Florida
        { participantId: 'VGK', elo: 1615 },  // Vegas
        { participantId: 'TBL', elo: 1590 },  // Tampa Bay
        { participantId: 'NJD', elo: 1565 },  // New Jersey
        { participantId: 'TOR', elo: 1510 },  // Toronto
        { participantId: 'NYR', elo: 1470 },  // NY Rangers
        { participantId: 'NYI', elo: 1324 },  // NY Islanders (weakest)
      ];

      const results = await simulateBracket(teams, 'nhl-8', 10000);

      const colDist = results.get('COL');
      const nyiDist = results.get('NYI');
      if (!colDist || !nyiDist) throw new Error('COL or NYI result not found');

      // Colorado should have highest championship probability
      expect(colDist[0]).toBeGreaterThan(0.15); // >15%

      // NY Islanders should have lowest championship probability
      expect(nyiDist[0]).toBeLessThan(0.08); // <8%

      // Colorado should be more likely to win than NYI
      expect(colDist[0]).toBeGreaterThan(nyiDist[0]);

      // Probabilities should sum to 1
      const colSum = colDist.reduce((acc, p) => acc + p, 0);
      const nyiSum = nyiDist.reduce((acc, p) => acc + p, 0);

      expect(colSum).toBeCloseTo(1.0, 1);
      expect(nyiSum).toBeCloseTo(1.0, 1);
    });
  });
});
