/**
 * Probability Updater Service
 *
 * Updates probability distributions when real results come in.
 *
 * Key behaviors:
 * - Finished participants: Set to 100% at their placement, 0% elsewhere
 * - Unfinished participants: Re-run ICM calculation with remaining participants
 * - Handles partial results correctly
 */

import { findParticipantResultsBySportsSeasonId } from "~/models/participant-result";
import {
  getAllParticipantEVsForSeason,
  upsertParticipantEV,
  type ParticipantEV,
} from "~/models/participant-expected-value";
import { calculateICMFromOdds } from "./icm-calculator";
import type { ProbabilityDistribution } from "./ev-calculator";
import { database } from "~/database/context";
import * as schema from "~/database/schema";
import { eq } from "drizzle-orm";

/**
 * Result of probability update operation
 */
export interface ProbabilityUpdateResult {
  finishedParticipants: number;
  unfishedParticipants: number;
  updated: number;
  errors: string[];
}

/**
 * Before/after probabilities for display
 */
export interface ProbabilityComparison {
  participantId: string;
  participantName: string;
  before: number[]; // [P(1st), P(2nd), ..., P(8th)]
  after: number[]; // [P(1st), P(2nd), ..., P(8th)]
  status: 'finished' | 'recalculated' | 'unchanged';
}

/**
 * Convert probability array to ProbabilityDistribution
 */
function arrayToProbabilityDistribution(probs: number[]): ProbabilityDistribution {
  return {
    probFirst: probs[0],
    probSecond: probs[1],
    probThird: probs[2],
    probFourth: probs[3],
    probFifth: probs[4],
    probSixth: probs[5],
    probSeventh: probs[6],
    probEighth: probs[7],
  };
}

/**
 * Convert ParticipantEV to probability array
 */
function evToProbabilityArray(ev: ParticipantEV): number[] {
  return [
    parseFloat(ev.probFirst),
    parseFloat(ev.probSecond),
    parseFloat(ev.probThird),
    parseFloat(ev.probFourth),
    parseFloat(ev.probFifth),
    parseFloat(ev.probSixth),
    parseFloat(ev.probSeventh),
    parseFloat(ev.probEighth),
  ];
}

/**
 * Create a probability distribution where participant finished at a specific position
 *
 * @param finalPosition The position where participant finished
 *   - 1-8: 100% at that position, 0% elsewhere
 *   - 0: Eliminated (didn't make playoffs) - 0% for all positions
 *   - >8: Finished outside scoring - 0% for all positions
 * @returns Array of probabilities with 100% at finalPosition (if 1-8), or all 0%
 */
function createFinishedProbabilities(finalPosition: number): number[] {
  const probs = [0, 0, 0, 0, 0, 0, 0, 0];

  // Handle positions 1-8
  if (finalPosition >= 1 && finalPosition <= 8) {
    probs[finalPosition - 1] = 1.0; // 100% at their position
  }
  // finalPosition = 0 (eliminated) or > 8 (finished outside top 8)
  // Keep all probabilities at 0%

  return probs;
}

/**
 * Update probabilities for a sports season after results come in
 *
 * Process:
 * 1. Get all participant results (finished participants)
 * 2. Get all existing participant EVs
 * 3. For finished participants: set 100% at their placement
 * 4. For unfinished participants: recalculate using ICM with remaining participants
 *
 * @param sportsSeasonId Sports season to update
 * @param recalculateUnfinished Whether to recalculate unfinished participants (default true)
 * @returns Update result summary
 */
export async function updateProbabilitiesAfterResult(
  sportsSeasonId: string,
  recalculateUnfinished = true
): Promise<ProbabilityUpdateResult> {
  const errors: string[] = [];
  let updated = 0;

  try {
    // Get all results (finished participants)
    const results = await findParticipantResultsBySportsSeasonId(sportsSeasonId);

    // Get all existing EVs
    const existingEVs = await getAllParticipantEVsForSeason(sportsSeasonId);

    // Create map of participantId -> finalPosition
    const finishedMap = new Map(
      results
        .filter(r => r.finalPosition !== null)
        .map(r => [r.participantId, r.finalPosition ?? 0])
    );

    // Update finished participants
    // Use default scoring rules (we only care about setting probabilities, not EV for finished)
    const defaultScoringRules = {
      pointsFor1st: 100,
      pointsFor2nd: 70,
      pointsFor3rd: 50,
      pointsFor4th: 40,
      pointsFor5th: 25,
      pointsFor6th: 25,
      pointsFor7th: 15,
      pointsFor8th: 15,
    };

    for (const [participantId, finalPosition] of finishedMap.entries()) {
      try {
        const probs = createFinishedProbabilities(finalPosition);
        const probabilities = arrayToProbabilityDistribution(probs);

        await upsertParticipantEV({
          participantId,
          sportsSeasonId,
          probabilities,
          scoringRules: defaultScoringRules,
          source: 'manual', // Result is from actual outcome
        });

        updated++;
      } catch (error) {
        errors.push(`Failed to update participant ${participantId}: ${error}`);
      }
    }

    // Recalculate unfinished participants if requested
    if (recalculateUnfinished) {
      const unfinishedEVs = existingEVs.filter(
        ev => !finishedMap.has(ev.participantId)
      );

      if (unfinishedEVs.length > 0) {
        // Get their current championship probabilities (use existing P(1st) as proxy)
        const unfinishedOdds = unfinishedEVs.map(ev => {
          const pFirst = parseFloat(ev.probFirst);
          // Convert probability back to odds (approximate)
          // probability = 100 / (odds + 100) => odds = (100 / probability) - 100
          const odds = pFirst > 0 ? Math.round((100 / pFirst) - 100) : 100000;

          return {
            participantId: ev.participantId,
            odds: odds,
          };
        });

        // Recalculate ICM for unfinished participants
        const icmResults = calculateICMFromOdds(unfinishedOdds);

        // Update each unfinished participant
        for (const [participantId, icmResult] of icmResults.entries()) {
          try {
            const probs = [
              icmResult.probabilities.first,
              icmResult.probabilities.second,
              icmResult.probabilities.third,
              icmResult.probabilities.fourth,
              icmResult.probabilities.fifth,
              icmResult.probabilities.sixth,
              icmResult.probabilities.seventh,
              icmResult.probabilities.eighth,
            ];

            const probabilities = arrayToProbabilityDistribution(probs);

            await upsertParticipantEV({
              participantId,
              sportsSeasonId,
              probabilities,
              scoringRules: defaultScoringRules,
              source: 'futures_odds', // Recalculated from remaining odds
            });

            updated++;
          } catch (error) {
            errors.push(`Failed to recalculate participant ${participantId}: ${error}`);
          }
        }
      }
    }

    return {
      finishedParticipants: finishedMap.size,
      unfishedParticipants: existingEVs.length - finishedMap.size,
      updated,
      errors,
    };
  } catch (error) {
    errors.push(`Failed to update probabilities: ${error}`);
    return {
      finishedParticipants: 0,
      unfishedParticipants: 0,
      updated,
      errors,
    };
  }
}

/**
 * Get before/after comparison of probabilities for display
 *
 * Shows what will change when updateProbabilitiesAfterResult runs.
 * Useful for preview before committing changes.
 *
 * @param sportsSeasonId Sports season to preview
 * @returns Array of probability comparisons
 */
export async function previewProbabilityUpdate(
  sportsSeasonId: string
): Promise<ProbabilityComparison[]> {
  const db = database();

  // Get all results (finished participants)
  const results = await findParticipantResultsBySportsSeasonId(sportsSeasonId);

  // Get all existing EVs with participant names
  const existingEVs = await db.query.participantExpectedValues.findMany({
    where: eq(schema.participantExpectedValues.sportsSeasonId, sportsSeasonId),
    with: {
      participant: true,
    },
  });

  // Create map of participantId -> finalPosition
  const finishedMap = new Map(
    results
      .filter(r => r.finalPosition !== null)
      .map(r => [r.participantId, r.finalPosition ?? 0])
  );

  const comparisons: ProbabilityComparison[] = [];

  for (const ev of existingEVs) {
    const before = evToProbabilityArray(ev);
    const finalPosition = finishedMap.get(ev.participantId);

    let after: number[];
    let status: 'finished' | 'recalculated' | 'unchanged';

    if (finalPosition !== undefined) {
      // Finished participant
      after = createFinishedProbabilities(finalPosition);
      status = 'finished';
    } else {
      // For preview, we'd need to recalculate - for now just show unchanged
      // In a real implementation, we'd run the ICM calculation here too
      after = before;
      status = 'unchanged';
    }

    comparisons.push({
      participantId: ev.participantId,
      participantName: ev.participant?.name || 'Unknown',
      before,
      after,
      status,
    });
  }

  return comparisons;
}
