const SIMILARITY_WEIGHTS = {
  functional_role_match: 0.20,
  opponent_defensive_rating: 0.14,
  pace: 0.15,
  lineup_context: 0.12,
  rest_days: 0.09,
  travel_fatigue: 0.08,
  game_importance: 0.07,
  referee_tendency: 0.06,
  score_state_context: 0.05,
  role_variance_match: 0.04,
};

/**
 * Calculate similarity score between two games based on weighted factors.
 * @param {object} gameA - First game context object
 * @param {object} gameB - Second game context object
 * @param {object} weights - Weight configuration (defaults to SIMILARITY_WEIGHTS)
 * @returns {number} Similarity score between 0 and 1
 */
function calculateSimilarityScore(gameA, gameB, weights = SIMILARITY_WEIGHTS) {
  let totalScore = 0;
  let totalWeight = 0;

  for (const [factor, weight] of Object.entries(weights)) {
    if (gameA[factor] !== undefined && gameB[factor] !== undefined) {
      const maxVal = Math.max(Math.abs(gameA[factor]), Math.abs(gameB[factor]), 1);
      const diff = Math.abs(gameA[factor] - gameB[factor]) / maxVal;
      const similarity = Math.max(0, 1 - diff);
      totalScore += similarity * weight;
      totalWeight += weight;
    }
  }

  if (totalWeight === 0) return 0;
  return Math.min(1, Math.max(0, totalScore / totalWeight));
}

/**
 * Find the most similar historical games to a target game.
 * @param {object} targetGame - The game to match against
 * @param {Array} historicalGames - Array of historical game objects
 * @param {number} minInstances - Minimum matches required (default 15)
 * @returns {object} { games: sorted matches, confidence: HIGH|LOW, usedSeasonAvg: boolean }
 */
function findSimilarGames(targetGame, historicalGames, minInstances = 15) {
  if (!historicalGames || historicalGames.length === 0) {
    return { games: [], confidence: 'LOW', usedSeasonAvg: true };
  }

  const scored = historicalGames.map(game => ({
    ...game,
    similarityScore: calculateSimilarityScore(targetGame, game),
  }));

  scored.sort((a, b) => b.similarityScore - a.similarityScore);

  if (scored.length < minInstances) {
    return {
      games: scored,
      confidence: 'LOW',
      usedSeasonAvg: true,
      note: `Only ${scored.length} similar games found (min: ${minInstances}). Falling back to season averages.`,
    };
  }

  return {
    games: scored.slice(0, Math.max(minInstances, Math.floor(scored.length * 0.3))),
    confidence: 'HIGH',
    usedSeasonAvg: false,
  };
}

/**
 * Calculate posterior distribution from similar games.
 * @param {Array} similarGames - Array of game objects with a stat value
 * @returns {object} { mean, stddev, ci_low, ci_high, n }
 */
function getPosteriorDistribution(similarGames) {
  if (!similarGames || similarGames.length === 0) {
    return { mean: 0, stddev: 0, ci_low: 0, ci_high: 0, n: 0 };
  }

  const values = similarGames.map(g => g.statValue || 0);
  const n = values.length;
  const mean = values.reduce((sum, v) => sum + v, 0) / n;
  const variance = values.reduce((sum, v) => sum + Math.pow(v - mean, 2), 0) / (n - 1 || 1);
  const stddev = Math.sqrt(variance);
  const zScore = 1.96; // 95% CI
  const se = stddev / Math.sqrt(n);

  return {
    mean: Math.round(mean * 100) / 100,
    stddev: Math.round(stddev * 100) / 100,
    ci_low: Math.round((mean - zScore * se) * 100) / 100,
    ci_high: Math.round((mean + zScore * se) * 100) / 100,
    n,
  };
}

module.exports = {
  SIMILARITY_WEIGHTS,
  calculateSimilarityScore,
  findSimilarGames,
  getPosteriorDistribution,
};
