/**
 * Leaderboard Prize Pool System Tests
 */

import { describe, it, expect, beforeEach } from 'vitest';
import {
  createPrizePool,
  calculatePrizeAmounts,
  distributePrizes,
  shouldClosePrizePool,
  getActivePrizePools,
  getCompletedPrizePools,
  calculateTotalPrizePool,
  getPlayerPrizeEarnings,
  getTopWinners,
  calculatePrizePoolStats,
  generatePrizePoolReport,
  STANDARD_DISTRIBUTIONS,
  type PrizePool,
} from './leaderboardPrizePool';

describe('Leaderboard Prize Pool System', () => {
  let weeklyPool: PrizePool;
  let monthlyPool: PrizePool;

  beforeEach(() => {
    weeklyPool = createPrizePool('neon-nights', 'weekly', 1000);
    monthlyPool = createPrizePool('neon-nights', 'monthly', 5000);
  });

  describe('createPrizePool', () => {
    it('should create weekly prize pool', () => {
      expect(weeklyPool.period).toBe('weekly');
      expect(weeklyPool.totalAmount).toBe(1000);
      expect(weeklyPool.status).toBe('active');
    });

    it('should create monthly prize pool', () => {
      expect(monthlyPool.period).toBe('monthly');
      expect(monthlyPool.totalAmount).toBe(5000);
      expect(monthlyPool.status).toBe('active');
    });

    it('should set correct expiration for weekly pool', () => {
      const expirationMs = weeklyPool.endDate.getTime() - weeklyPool.startDate.getTime();
      const sevenDaysMs = 7 * 24 * 60 * 60 * 1000;

      expect(Math.abs(expirationMs - sevenDaysMs)).toBeLessThan(1000);
    });

    it('should calculate distributions', () => {
      expect(weeklyPool.distributions.length).toBeGreaterThan(0);
      expect(weeklyPool.distributions[0].rank).toBe(1);
    });

    it('should ensure distributions sum to 100%', () => {
      const totalPercentage = weeklyPool.distributions.reduce((sum, d) => sum + d.percentage, 0);
      expect(totalPercentage).toBe(100);
    });

    it('should calculate correct amounts', () => {
      const firstPlace = weeklyPool.distributions[0];
      const expectedAmount = (1000 * firstPlace.percentage) / 100;

      expect(firstPlace.amount).toBeCloseTo(expectedAmount, 2);
    });
  });

  describe('calculatePrizeAmounts', () => {
    it('should calculate amounts for all distributions', () => {
      const amounts = calculatePrizeAmounts(weeklyPool);

      expect(amounts.length).toBe(weeklyPool.distributions.length);
      amounts.forEach((amount) => {
        expect(amount.amount).toBeGreaterThan(0);
      });
    });

    it('should sum to total pool amount', () => {
      const amounts = calculatePrizeAmounts(weeklyPool);
      const total = amounts.reduce((sum, a) => sum + a.amount, 0);

      expect(total).toBeCloseTo(weeklyPool.totalAmount, 1);
    });
  });

  describe('distributePrizes', () => {
    it('should distribute prizes to top players', () => {
      const topPlayers = [
        { userId: 1, score: 1000 },
        { userId: 2, score: 950 },
        { userId: 3, score: 900 },
      ];

      const winners = distributePrizes(weeklyPool, topPlayers);

      expect(winners.length).toBe(3);
      expect(winners[0].userId).toBe(1);
      expect(winners[0].rank).toBe(1);
    });

    it('should assign correct amounts', () => {
      const topPlayers = [
        { userId: 1, score: 1000 },
        { userId: 2, score: 950 },
      ];

      const winners = distributePrizes(weeklyPool, topPlayers);

      expect(winners[0].amount).toBeGreaterThan(winners[1].amount);
    });

    it('should handle fewer players than prize tiers', () => {
      const topPlayers = [
        { userId: 1, score: 1000 },
        { userId: 2, score: 950 },
      ];

      const winners = distributePrizes(weeklyPool, topPlayers);

      expect(winners.length).toBe(2);
    });
  });

  describe('shouldClosePrizePool', () => {
    it('should return false for new pool', () => {
      expect(shouldClosePrizePool(weeklyPool)).toBe(false);
    });

    it('should return true for expired pool', () => {
      weeklyPool.endDate = new Date(Date.now() - 1000);

      expect(shouldClosePrizePool(weeklyPool)).toBe(true);
    });
  });

  describe('getActivePrizePools', () => {
    it('should return active pools', () => {
      const pools = [weeklyPool, monthlyPool];
      const active = getActivePrizePools(pools, 'neon-nights');

      expect(active.length).toBe(2);
    });

    it('should exclude expired pools', () => {
      weeklyPool.endDate = new Date(Date.now() - 1000);
      const pools = [weeklyPool, monthlyPool];
      const active = getActivePrizePools(pools, 'neon-nights');

      expect(active.length).toBe(1);
    });

    it('should exclude completed pools', () => {
      weeklyPool.status = 'completed';
      const pools = [weeklyPool, monthlyPool];
      const active = getActivePrizePools(pools, 'neon-nights');

      expect(active.length).toBe(1);
    });

    it('should filter by game ID', () => {
      const pools = [weeklyPool, monthlyPool];
      const active = getActivePrizePools(pools, 'different-game');

      expect(active.length).toBe(0);
    });
  });

  describe('getCompletedPrizePools', () => {
    it('should return completed but not distributed pools', () => {
      weeklyPool.status = 'completed';
      const pools = [weeklyPool, monthlyPool];
      const completed = getCompletedPrizePools(pools, 'neon-nights');

      expect(completed.length).toBe(1);
      expect(completed[0].status).toBe('completed');
    });

    it('should exclude distributed pools', () => {
      weeklyPool.status = 'completed';
      weeklyPool.distributedAt = new Date();
      const pools = [weeklyPool, monthlyPool];
      const completed = getCompletedPrizePools(pools, 'neon-nights');

      expect(completed.length).toBe(0);
    });
  });

  describe('calculateTotalPrizePool', () => {
    it('should sum active pool amounts', () => {
      const pools = [weeklyPool, monthlyPool];
      const total = calculateTotalPrizePool(pools, 'neon-nights', 'weekly');

      expect(total).toBe(1000);
    });

    it('should return 0 for no active pools', () => {
      weeklyPool.status = 'completed';
      const pools = [weeklyPool, monthlyPool];
      const total = calculateTotalPrizePool(pools, 'neon-nights', 'weekly');

      expect(total).toBe(0);
    });
  });

  describe('getPlayerPrizeEarnings', () => {
    it('should calculate player earnings from distributed pools', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
        { userId: 2, rank: 2, amount: 200 },
      ];

      monthlyPool.status = 'distributed';
      monthlyPool.winners = [
        { userId: 1, rank: 1, amount: 1500 },
      ];

      const pools = [weeklyPool, monthlyPool];
      const earnings = getPlayerPrizeEarnings(pools, 1);

      expect(earnings).toBe(1800);
    });

    it('should return 0 for player with no winnings', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = [
        { userId: 2, rank: 1, amount: 300 },
      ];

      const pools = [weeklyPool];
      const earnings = getPlayerPrizeEarnings(pools, 1);

      expect(earnings).toBe(0);
    });
  });

  describe('getTopWinners', () => {
    it('should return top winners sorted by earnings', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
        { userId: 2, rank: 2, amount: 200 },
        { userId: 3, rank: 3, amount: 150 },
      ];

      const pools = [weeklyPool];
      const topWinners = getTopWinners(pools, 10);

      expect(topWinners[0].userId).toBe(1);
      expect(topWinners[0].totalEarnings).toBe(300);
    });

    it('should respect limit parameter', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = Array.from({ length: 20 }, (_, i) => ({
        userId: i + 1,
        rank: i + 1,
        amount: 100 - i,
      }));

      const pools = [weeklyPool];
      const topWinners = getTopWinners(pools, 5);

      expect(topWinners.length).toBe(5);
    });

    it('should aggregate earnings from multiple pools', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
      ];

      monthlyPool.status = 'distributed';
      monthlyPool.winners = [
        { userId: 1, rank: 1, amount: 1500 },
      ];

      const pools = [weeklyPool, monthlyPool];
      const topWinners = getTopWinners(pools, 10);

      expect(topWinners[0].totalEarnings).toBe(1800);
      expect(topWinners[0].winCount).toBe(2);
    });
  });

  describe('calculatePrizePoolStats', () => {
    it('should calculate stats for empty pools', () => {
      const stats = calculatePrizePoolStats([]);

      expect(stats.totalPoolsCreated).toBe(0);
      expect(stats.totalDistributed).toBe(0);
      expect(stats.averagePoolSize).toBe(0);
    });

    it('should calculate stats for distributed pools', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
        { userId: 2, rank: 2, amount: 200 },
      ];

      const pools = [weeklyPool];
      const stats = calculatePrizePoolStats(pools);

      expect(stats.totalPoolsCreated).toBe(1);
      expect(stats.totalDistributed).toBe(500);
      expect(stats.totalWinnersRewarded).toBe(2);
      expect(stats.highestPayout).toBe(300);
      expect(stats.lowestPayout).toBe(200);
    });
  });

  describe('generatePrizePoolReport', () => {
    it('should generate report for pool', () => {
      weeklyPool.status = 'distributed';
      weeklyPool.distributedAt = new Date();
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
        { userId: 2, rank: 2, amount: 200 },
      ];

      const report = generatePrizePoolReport(weeklyPool);

      expect(report.poolId).toBe(weeklyPool.id);
      expect(report.gameId).toBe('neon-nights');
      expect(report.period).toBe('weekly');
      expect(report.totalAmount).toBe(1000);
      expect(report.winners.length).toBe(2);
    });

    it('should calculate percentages correctly', () => {
      weeklyPool.winners = [
        { userId: 1, rank: 1, amount: 300 },
      ];

      const report = generatePrizePoolReport(weeklyPool);

      expect(report.winners[0].percentage).toBeCloseTo(30, 1);
    });
  });
});
