/**
 * Composite Score Engine
 *
 * Blends signals from Grok, PIFF, and DIGIMON into a unified composite confidence score.
 * This is the "Rain Main" scoring system.
 */

import { PiffLeg } from './piff';
import { DigimonPick } from './digimon';
import { TeamDvp } from './dvp';
import { CornerScoutMatch, isSoccerLeague } from './corner-scout';
import { computeKenPomMOVScore, KenPomMatchup } from './kenpom';

export interface CompositeResult {
  compositeConfidence: number;  // 0-1 blended confidence
  stormCategory: number;        // 1-5
  modelSignals: {
    grok: { confidence: number; valueRating: number } | null;
    piff: { avgEdge: number; legCount: number; topPicks: Array<{ name: string; stat: string; line: number; edge: number; direction: string; tier: string; szn_hr?: number }> } | null;
    digimon: { available: boolean; lockCount: number; avgMissRate: number; topPicks: Array<{ player: string; prop: string; line: number; missRate: number; verdict: string }> } | null;
    dvp: { home: Record<string, Array<{ stat: string; rank: number; tier: string }>> | null; away: Record<string, Array<{ stat: string; rank: number; tier: string }>> | null } | null;
  };
  edgeBreakdown: {
    grokScore: number;
    piffScore: number;
    digimonScore: number | null;
    cornerScoutScore?: number;
    kenpomScore?: number;
    kenpomMOV?: number;
    weights: Record<string, number>;
  };
  compositeVersion: string;
}

const COMPOSITE_VERSION = 'v2';

// Weights: Grok 0.35 | PIFF 0.35 | DIGIMON 0.30 (NBA)
// NCAAB with KenPom: Grok 0.55 | KenPom 0.45 (no PIFF)
// Soccer with Corner Scout: Grok 0.40 | PIFF 0.40 | Corner Scout 0.20
// Default (non-NBA, non-NCAAB): Grok 0.50 | PIFF 0.50
const WEIGHTS_NBA = { grok: 0.35, piff: 0.35, digimon: 0.30 };
const WEIGHTS_NCAAB = { grok: 0.55, kenpom: 0.45 };
const WEIGHTS_SOCCER = { grok: 0.40, piff: 0.40, cornerScout: 0.20 };
const WEIGHTS_DEFAULT = { grok: 0.50, piff: 0.50, digimon: 0 };

function clamp(val: number, min: number, max: number): number {
  return Math.max(min, Math.min(max, val));
}

function normalizeWeights<T extends Record<string, number>>(weights: T): T {
  const total = Object.values(weights).reduce((sum, value) => sum + (value > 0 ? value : 0), 0);
  if (total <= 0) return weights;

  return Object.fromEntries(
    Object.entries(weights).map(([key, value]) => [key, value > 0 ? value / total : 0]),
  ) as T;
}

function getStormCategory(confidence: number, valueRating: number): number {
  if (confidence >= 0.80 && valueRating >= 8) return 5;
  if (confidence >= 0.70 && valueRating >= 7) return 4;
  if (confidence >= 0.60 && valueRating >= 5) return 3;
  if (confidence >= 0.50) return 2;
  return 1;
}

/**
 * Calculate composite confidence from all model signals.
 */
export function calculateComposite(params: {
  grokConfidence: number;
  grokValueRating: number;
  piffProps: PiffLeg[];
  digimonPicks: DigimonPick[];
  dvp: { home: TeamDvp | null; away: TeamDvp | null };
  league: string;
  kenpomMatchup?: any;
  cornerScoutData?: CornerScoutMatch | null;
}): CompositeResult {
  const { grokConfidence, grokValueRating, piffProps, digimonPicks, dvp, league, kenpomMatchup, cornerScoutData } = params;
  const isNba = league === 'nba';
  const isNcaab = league === 'ncaab';
  const isSoccer = isSoccerLeague(league);
  const hasKenPom = isNcaab && kenpomMatchup && (kenpomMatchup.home || kenpomMatchup.away);
  const hasCornerScout = isSoccer && cornerScoutData && cornerScoutData.projection !== null;
  // NBA without DIGIMON: redistribute DIGIMON weight to Grok (not 50/50)
  const WEIGHTS_NBA_NO_DIGIMON = { grok: 0.60, piff: 0.40, digimon: 0 };
  const baseWeights = isNba && digimonPicks.length > 0
    ? WEIGHTS_NBA
    : isNba ? WEIGHTS_NBA_NO_DIGIMON : WEIGHTS_DEFAULT;

  // Grok score: direct confidence (already 0-1)
  const grokScore = clamp(grokConfidence, 0, 1);

  // PIFF score: logistic mapping of avg edge (0-1 range)
  // PIFF 3.0 edges range 0.10-0.80; use tanh to spread across 0.5-0.95 without ceiling saturation
  // edge=0 → 0.50, edge=0.20 → 0.60, edge=0.40 → 0.70, edge=0.60 → 0.78, edge=0.80 → 0.84
  // Also factor in leg count: more legs = more reliable signal
  let piffScore = 0.5; // neutral baseline
  if (piffProps.length > 0) {
    const avgEdge = piffProps.reduce((sum, p) => sum + p.edge, 0) / piffProps.length;
    const baseScore = 0.5 + 0.45 * Math.tanh(avgEdge * 1.8);
    // Leg count confidence: 1 leg = 70% weight, 3+ legs = 100%
    const legConfidence = clamp(0.7 + (piffProps.length - 1) * 0.15, 0.7, 1.0);
    piffScore = 0.5 + (baseScore - 0.5) * legConfidence;
  }

  // DIGIMON score: inverse miss rate (lower miss = higher score)
  let digimonScore: number | null = null;
  if (isNba && digimonPicks.length > 0) {
    const actionablePicks = digimonPicks.filter(p => p.verdict !== 'SKIP');
    if (actionablePicks.length > 0) {
      const avgMissRate = actionablePicks.reduce((sum, p) => sum + p.missRate, 0) / actionablePicks.length;
      digimonScore = clamp(1 - avgMissRate, 0, 1);
    }
  }

  // KenPom score for NCAAB: AdjEM-based MOV prediction (from RAG/Mathletics concept)
  // Uses power rating differential + home court advantage for a calibrated confidence signal
  let kenpomScore: number | null = null;
  let kenpomGamesPlayed = 30;
  if (hasKenPom) {
    const movResult = computeKenPomMOVScore(kenpomMatchup as KenPomMatchup);
    kenpomScore = movResult.confidence;
    kenpomGamesPlayed = movResult.gamesPlayed;

    // Games-played confidence weighting (Bayesian concept from RAG):
    // Early season ratings are less reliable — discount KenPom weight
    if (kenpomGamesPlayed < 15) {
      // Very early season: reduce KenPom influence by 30%
      kenpomScore = 0.5 + (kenpomScore - 0.5) * 0.70;
    } else if (kenpomGamesPlayed >= 25) {
      // Late season: KenPom ratings are well-calibrated, boost slightly
      kenpomScore = clamp(kenpomScore * 1.05, 0.5, 0.98);
    }
  }

  // Corner Scout score for soccer: convert projection edge into 0-1 confidence signal
  let cornerScoutScore: number | null = null;
  if (hasCornerScout && cornerScoutData) {
    const edge = cornerScoutData.edge ?? 0;
    const absEdge = Math.abs(edge);
    // Edge of 0 = neutral (0.5), edge of ±3+ corners = strong signal (approaching 0.9/0.1)
    // Scale: each corner of edge ≈ 0.13 confidence boost from neutral
    cornerScoutScore = clamp(0.5 + (absEdge * 0.13), 0.5, 0.95);
    // Boost further if rating is strong
    if (cornerScoutData.rating === 'STRONG BET') {
      cornerScoutScore = clamp(cornerScoutScore + 0.05, 0.5, 0.98);
    } else if (cornerScoutData.rating === 'LEAN') {
      cornerScoutScore = clamp(cornerScoutScore + 0.02, 0.5, 0.95);
    }
  }

  const hasPiff = piffProps.length > 0;
  const hasDigimon = digimonScore !== null;
  const effectiveDefaultWeights = normalizeWeights({
    grok: baseWeights.grok,
    piff: hasPiff ? baseWeights.piff : 0,
    digimon: hasDigimon ? baseWeights.digimon : 0,
  });
  const effectiveSoccerWeights = normalizeWeights({
    grok: WEIGHTS_SOCCER.grok,
    piff: hasPiff ? WEIGHTS_SOCCER.piff : 0,
    cornerScout: cornerScoutScore !== null ? WEIGHTS_SOCCER.cornerScout : 0,
  });

  // Weighted composite
  let compositeConfidence: number;
  if (hasDigimon && effectiveDefaultWeights.digimon > 0) {
    compositeConfidence = (
      grokScore * effectiveDefaultWeights.grok +
      piffScore * effectiveDefaultWeights.piff +
      (digimonScore as number) * effectiveDefaultWeights.digimon
    );
  } else if (hasKenPom && kenpomScore !== null) {
    // NCAAB with KenPom: 2-signal blend (no PIFF)
    compositeConfidence = (
      grokScore * WEIGHTS_NCAAB.grok +
      kenpomScore * WEIGHTS_NCAAB.kenpom
    );
  } else if (isNcaab) {
    // NCAAB without KenPom: Grok only (no PIFF for NCAAB)
    compositeConfidence = grokScore;
  } else if (hasCornerScout && cornerScoutScore !== null) {
    // Soccer with Corner Scout: 3-signal blend
    compositeConfidence = (
      grokScore * effectiveSoccerWeights.grok +
      piffScore * effectiveSoccerWeights.piff +
      cornerScoutScore * effectiveSoccerWeights.cornerScout
    );
  } else {
    // Default: only blend real available signals. Missing PIFF should not
    // silently pull the score back toward a fake neutral 0.5 baseline.
    compositeConfidence = (
      grokScore * effectiveDefaultWeights.grok +
      piffScore * effectiveDefaultWeights.piff
    );
  }

  compositeConfidence = clamp(compositeConfidence, 0, 1);

  const stormCategory = getStormCategory(compositeConfidence, grokValueRating);

  // Build model signals for response
  const piffSignals = piffProps.length > 0 ? {
    avgEdge: Math.round(piffProps.reduce((s, p) => s + p.edge, 0) / piffProps.length * 1000) / 10,
    legCount: piffProps.length,
    topPicks: piffProps
      .sort((a, b) => b.edge - a.edge)
      .slice(0, 3)
      .map(p => ({
        name: p.name, stat: p.stat, line: p.line, edge: Math.round(p.edge * 1000) / 10,
        direction: p.direction || 'over', tier: p.tier_label || 'T3_SOLID', szn_hr: p.szn_hr,
      })),
  } : null;

  const digimonSignals = isNba && digimonPicks.length > 0 ? {
    available: true,
    lockCount: digimonPicks.filter(p => p.verdict === 'LOCK').length,
    avgMissRate: Math.round(
      digimonPicks.reduce((s, p) => s + p.missRate, 0) / digimonPicks.length * 100
    ) / 100,
    topPicks: digimonPicks
      .filter(p => p.verdict === 'LOCK' || p.verdict === 'PLAY')
      .slice(0, 3)
      .map(p => ({ player: p.player, prop: p.prop, line: p.line, missRate: p.missRate, verdict: p.verdict })),
  } : null;

  const dvpSignals = dvp.home || dvp.away ? {
    home: formatDvpCompact(dvp.home),
    away: formatDvpCompact(dvp.away),
  } : null;

  return {
    compositeConfidence,
    stormCategory,
    modelSignals: {
      grok: { confidence: grokConfidence, valueRating: grokValueRating },
      piff: isNcaab ? null : piffSignals,
      digimon: digimonSignals,
      dvp: dvpSignals,
    },
    edgeBreakdown: {
      grokScore: Math.round(grokScore * 1000) / 1000,
      piffScore: isNcaab ? 0 : Math.round(piffScore * 1000) / 1000,
      digimonScore: digimonScore !== null ? Math.round(digimonScore * 1000) / 1000 : null,
      cornerScoutScore: cornerScoutScore !== null ? Math.round(cornerScoutScore * 1000) / 1000 : undefined,
      kenpomScore: kenpomScore !== null ? Math.round(kenpomScore * 1000) / 1000 : undefined,
      kenpomMOV: hasKenPom ? Math.round(computeKenPomMOVScore(kenpomMatchup as KenPomMatchup).predictedMOV * 10) / 10 : undefined,
      weights: isNcaab
        ? WEIGHTS_NCAAB as Record<string, number>
        : hasCornerScout && cornerScoutScore !== null
          ? effectiveSoccerWeights as Record<string, number>
          : effectiveDefaultWeights as Record<string, number>,
    },
    compositeVersion: COMPOSITE_VERSION,
  };
}

function formatDvpCompact(team: TeamDvp | null): Record<string, Array<{ stat: string; rank: number; tier: string }>> | null {
  if (!team) return null;
  const byPos: Record<string, Array<{ stat: string; rank: number; tier: string }>> = {};
  for (const r of team.ranks) {
    if (!byPos[r.position]) byPos[r.position] = [];
    byPos[r.position].push({ stat: r.stat, rank: r.rank, tier: r.tier });
  }
  return Object.keys(byPos).length > 0 ? byPos : null;
}
