import { logger } from "~/lib/logger";

/**
 * ICM (Independent Chip Model) Calculator - Monte Carlo Simulation
 *
 * Calculates probability distributions for tournament placements based on
 * championship odds using Monte Carlo simulation (100,000+ iterations).
 *
 * Algorithm:
 * 1. Pick 1st place: weighted random selection based on championship probabilities
 * 2. Remove winner, pick 2nd place: weighted random from remaining
 * 3. Continue for all 8 positions
 * 4. Repeat 100,000 times
 * 5. Calculate probabilities from simulation results
 *
 * This approach is much faster and more memory-efficient than exact recursive
 * calculation for large fields (30+ participants). Results converge to true ICM
 * probabilities with sufficient iterations.
 */

/**
 * Participant with championship probability (chip stack)
 */
export interface ParticipantChips {
  participantId: string;
  championshipProbability: number; // 0-1 (e.g., 0.154 = 15.4%)
}

/**
 * ICM result for a single participant
 * Probabilities for finishing in each placement
 */
export interface ICMResult {
  participantId: string;
  probabilities: {
    first: number;
    second: number;
    third: number;
    fourth: number;
    fifth: number;
    sixth: number;
    seventh: number;
    eighth: number;
  };
}

/**
 * Simulate one tournament using weighted random selection
 *
 * @param participants Participants with championship probabilities (weights)
 * @param scoringPlaces Number of positions to simulate
 * @returns Array of participant IDs in finish order [1st, 2nd, ..., 8th]
 */
function simulateTournament(
  participants: ParticipantChips[],
  scoringPlaces: number
): string[] {
  const placements: string[] = [];
  let remaining = [...participants];

  for (let pos = 0; pos < scoringPlaces && remaining.length > 0; pos++) {
    // Calculate total weight of remaining participants
    const totalWeight = remaining.reduce((sum, p) => sum + p.championshipProbability, 0);

    // Weighted random selection
    const rand = Math.random() * totalWeight;
    let cumulative = 0;
    let selected = remaining[0];

    for (const p of remaining) {
      cumulative += p.championshipProbability;
      if (rand <= cumulative) {
        selected = p;
        break;
      }
    }

    // Record placement and remove from remaining
    placements.push(selected.participantId);
    remaining = remaining.filter(p => p.participantId !== selected.participantId);
  }

  return placements;
}

/**
 * Calculate ICM probability distribution for all participants using Monte Carlo simulation
 *
 * Simulates 100,000 tournaments using weighted random selection based on championship
 * probabilities. Much faster and more memory-efficient than exact recursive calculation
 * for large fields (30+ participants).
 *
 * @param participants Array of participants with championship probabilities
 * @param scoringPlaces Number of places that score points (default 8)
 * @param iterations Number of simulations to run (default 100,000)
 * @returns Map of participantId to probability distribution
 *
 * @example
 * const participants = [
 *   { participantId: 'OKC', championshipProbability: 0.364 }, // 36.4% (+175 odds)
 *   { participantId: 'BOS', championshipProbability: 0.300 },
 *   // ... 28 more NBA teams
 * ];
 * const results = calculateICM(participants);
 * // Results for all 30 teams, each with P(1st) through P(8th) from simulation
 */
export function calculateICM(
  participants: ParticipantChips[],
  scoringPlaces = 8,
  iterations = 100000
): Map<string, ICMResult> {
  if (participants.length === 0) {
    return new Map();
  }

  // Normalize championship probabilities to sum to 1.0 (remove bookmaker vig)
  const totalProb = participants.reduce((sum, p) => sum + p.championshipProbability, 0);
  const normalized = participants.map(p => ({
    ...p,
    championshipProbability: totalProb > 0 ? p.championshipProbability / totalProb : 1 / participants.length,
  }));

  logger.log(`[ICM Simulation] Starting ${iterations.toLocaleString()} simulations for ${normalized.length} participants`);
  const startTime = Date.now();

  // Initialize counters for each participant
  const counts = new Map<string, number[]>();
  for (const p of normalized) {
    counts.set(p.participantId, Array(scoringPlaces).fill(0));
  }

  // Run simulations
  for (let iter = 0; iter < iterations; iter++) {
    const placements = simulateTournament(normalized, scoringPlaces);

    // Record results
    for (let pos = 0; pos < placements.length; pos++) {
      const participantCounts = counts.get(placements[pos]);
      if (participantCounts) participantCounts[pos]++;
    }

    // Progress logging every 10,000 iterations
    if ((iter + 1) % 10000 === 0) {
      const progress = ((iter + 1) / iterations * 100).toFixed(0);
      const elapsed = Date.now() - startTime;
      const rate = (iter + 1) / (elapsed / 1000);
      logger.log(`[ICM Simulation] ${progress}% complete (${(iter + 1).toLocaleString()}/${iterations.toLocaleString()}) - ${rate.toFixed(0)} sims/sec`);
    }
  }

  // Convert counts to probabilities
  const results = new Map<string, ICMResult>();

  for (const p of normalized) {
    const participantCounts = counts.get(p.participantId) ?? Array(scoringPlaces).fill(0);
    const probabilities = participantCounts.map(count => count / iterations);

    results.set(p.participantId, {
      participantId: p.participantId,
      probabilities: {
        first: probabilities[0] || 0,
        second: probabilities[1] || 0,
        third: probabilities[2] || 0,
        fourth: probabilities[3] || 0,
        fifth: probabilities[4] || 0,
        sixth: probabilities[5] || 0,
        seventh: probabilities[6] || 0,
        eighth: probabilities[7] || 0,
      },
    });
  }

  const elapsed = Date.now() - startTime;
  logger.log(`[ICM Simulation] Completed in ${(elapsed / 1000).toFixed(1)}s (${(iterations / (elapsed / 1000)).toFixed(0)} sims/sec)`);

  return results;
}

/**
 * Convert ICM result to array format for database storage
 *
 * @param icmResult ICM result object
 * @returns Array of 8 probabilities [P(1st), P(2nd), ..., P(8th)]
 */
export function icmResultToArray(icmResult: ICMResult): number[] {
  return [
    icmResult.probabilities.first,
    icmResult.probabilities.second,
    icmResult.probabilities.third,
    icmResult.probabilities.fourth,
    icmResult.probabilities.fifth,
    icmResult.probabilities.sixth,
    icmResult.probabilities.seventh,
    icmResult.probabilities.eighth,
  ];
}

/**
 * Convert American odds to implied probability
 *
 * @param odds American odds (e.g., +175, -200)
 * @returns Implied probability (0-1)
 */
function convertAmericanOddsToProbability(odds: number): number {
  if (odds > 0) {
    // Positive odds (underdog): probability = 100 / (odds + 100)
    return 100 / (odds + 100);
  } else {
    // Negative odds (favorite): probability = -odds / (-odds + 100)
    return -odds / (-odds + 100);
  }
}

/**
 * Convert futures odds to championship probabilities and calculate ICM
 *
 * Complete pipeline from odds to probability distributions.
 *
 * @param futuresOdds Array of {participantId, odds} with American odds
 * @param scoringPlaces Number of places that score points (default 8)
 * @returns Map of participantId to ICM result
 *
 * @example
 * const odds = [
 *   { participantId: 'OKC', odds: 175 },   // +175 (36.4% implied)
 *   { participantId: 'BOS', odds: -150 },  // -150 (60% implied)
 *   // ... more teams
 * ];
 * const results = calculateICMFromOdds(odds);
 */
export function calculateICMFromOdds(
  futuresOdds: Array<{ participantId: string; odds: number }>,
  scoringPlaces = 8
): Map<string, ICMResult> {
  // Convert odds to championship probabilities
  const participants: ParticipantChips[] = futuresOdds.map(({ participantId, odds }) => ({
    participantId,
    championshipProbability: convertAmericanOddsToProbability(odds),
  }));

  return calculateICM(participants, scoringPlaces);
}
