import 'dotenv/config';
import pool from '../db';
import {
  buildMlbBaselineOverride,
  computeHistoricalTeamPlusDecision,
  TEAM_PLUS_WEIGHTS,
  type TeamPlusLeague,
  type TeamPlusWeightProfile,
} from '../services/team-plus';
import type { SignalResult } from '../services/rie/types';

type BacktestRow = {
  league: TeamPlusLeague;
  eventId: string;
  homeTeam: string;
  awayTeam: string;
  homeShort: string;
  awayShort: string;
  startsAt: string;
  resolvedAt: string;
  predictedWinner: string;
  actualWinner: string;
  rieSignals: SignalResult[];
};

type Summary = {
  picks: number;
  wins: number;
  wr: number;
};

const CURRENT_LIVE_THRESHOLD: Partial<Record<TeamPlusLeague, number>> = {
  nhl: 0.68,
};

function summarize(rows: Array<{ winner: string; actual: string }>): Summary {
  const wins = rows.filter((row) => row.winner === row.actual).length;
  const picks = rows.length;
  return {
    picks,
    wins,
    wr: picks > 0 ? wins / picks : 0,
  };
}

function splitTrainTest<T>(rows: T[]): { train: T[]; test: T[] } {
  const cut = Math.max(1, Math.floor(rows.length * 0.7));
  return {
    train: rows.slice(0, cut),
    test: rows.slice(cut),
  };
}

function* buildCandidates(league: TeamPlusLeague): Generator<TeamPlusWeightProfile> {
  if (league === 'mlb') {
    for (const matchup of [0.32, 0.36, 0.40]) {
      for (const phase of [0.28, 0.32, 0.36]) {
        for (const fangraphs of [0.14, 0.20, 0.24]) {
          for (const piff of [0.04, 0.06, 0.10]) {
            const dvp = 1 - matchup - phase - fangraphs - piff;
            if (dvp < 0.02 || dvp > 0.10) continue;
            yield {
              ...TEAM_PLUS_WEIGHTS,
              mlb: {
                ...TEAM_PLUS_WEIGHTS.mlb,
                mlb_matchup: matchup,
                mlb_phase: phase,
                fangraphs,
                piff_team_plus: piff,
                dvp_team_plus: dvp,
              },
            };
          }
        }
      }
    }
    return;
  }

  if (league === 'nba') {
    for (const piff of [0.35, 0.45, 0.55]) {
      for (const digimon of [0.20, 0.30, 0.40]) {
        const dvp = 1 - piff - digimon;
        if (dvp < 0.10 || dvp > 0.35) continue;
        yield {
          ...TEAM_PLUS_WEIGHTS,
          nba: {
            ...TEAM_PLUS_WEIGHTS.nba,
            piff_team_plus: piff,
            digimon_team_plus: digimon,
            dvp_team_plus: dvp,
          },
        };
      }
    }
    return;
  }

  for (const piff of [0.55, 0.68, 0.8]) {
    const dvp = 1 - piff;
    yield {
      ...TEAM_PLUS_WEIGHTS,
      nhl: {
        ...TEAM_PLUS_WEIGHTS.nhl,
        piff_team_plus: piff,
        dvp_team_plus: dvp,
      },
    };
  }
}

async function loadRows(): Promise<BacktestRow[]> {
  const { rows } = await pool.query(
    `SELECT fa.league,
            fa.event_id,
            fa.predicted_winner,
            fa.actual_winner,
            fa.resolved_at,
            fc.home_team,
            fc.away_team,
            fc.starts_at,
            ev.home_short,
            ev.away_short,
            fc.model_signals
     FROM rm_forecast_accuracy_v2 fa
     JOIN rm_forecast_cache fc ON fc.event_id = fa.event_id
     LEFT JOIN rm_events ev ON ev.event_id = fa.event_id
     WHERE fa.model_version = 'rm_2.0'
       AND fa.forecast_type = 'spread'
       AND fa.league IN ('mlb', 'nba', 'nhl')
       AND fa.resolved_at >= NOW() - INTERVAL '120 days'
       AND fa.predicted_winner IS NOT NULL
       AND fa.actual_winner IS NOT NULL
       AND fc.model_signals ? 'rieSignals'
     ORDER BY fa.league, fa.resolved_at ASC`,
  );

  return rows
    .map((row: any) => ({
      league: row.league,
      eventId: row.event_id,
      homeTeam: row.home_team,
      awayTeam: row.away_team,
      homeShort: row.home_short || '',
      awayShort: row.away_short || '',
      startsAt: row.starts_at,
      resolvedAt: row.resolved_at,
      predictedWinner: row.predicted_winner,
      actualWinner: row.actual_winner,
      rieSignals: Array.isArray(row.model_signals?.rieSignals) ? row.model_signals.rieSignals : [],
    }))
    .filter((row) => row.homeShort && row.awayShort);
}

async function main(): Promise<void> {
  const rows = await loadRows();
  for (const league of ['mlb', 'nba', 'nhl'] as TeamPlusLeague[]) {
    const leagueRows = rows.filter((row) => row.league === league);
    const { train, test } = splitTrainTest(leagueRows);
    const baselineTrain = summarize(train.map((row) => ({ winner: row.predictedWinner, actual: row.actualWinner })));
    const baselineTest = summarize(test.map((row) => ({ winner: row.predictedWinner, actual: row.actualWinner })));

    let bestWeights: TeamPlusWeightProfile | null = null;
    let bestTrainWr = -1;

    for (const weights of buildCandidates(league)) {
      const trainSummary = summarize(train.map((row) => {
        const decision = computeHistoricalTeamPlusDecision({
          league: row.league,
          homeTeam: row.homeTeam,
          awayTeam: row.awayTeam,
          homeShort: row.homeShort,
          awayShort: row.awayShort,
          startsAt: row.startsAt,
          rieSignals: row.rieSignals,
          weightsOverride: weights,
        });
        return { winner: decision.winnerPick, actual: row.actualWinner };
      }));
      if (trainSummary.wr > bestTrainWr) {
        bestTrainWr = trainSummary.wr;
        bestWeights = weights;
      }
    }

    const tunedWeights = bestWeights || TEAM_PLUS_WEIGHTS;
    const tunedTrain = summarize(train.map((row) => {
      const decision = computeHistoricalTeamPlusDecision({
        league: row.league,
        homeTeam: row.homeTeam,
        awayTeam: row.awayTeam,
        homeShort: row.homeShort,
        awayShort: row.awayShort,
        startsAt: row.startsAt,
        rieSignals: row.rieSignals,
        weightsOverride: tunedWeights,
      });
      return { winner: decision.winnerPick, actual: row.actualWinner };
    }));
    const tunedTest = summarize(test.map((row) => {
      const decision = computeHistoricalTeamPlusDecision({
        league: row.league,
        homeTeam: row.homeTeam,
        awayTeam: row.awayTeam,
        homeShort: row.homeShort,
        awayShort: row.awayShort,
        startsAt: row.startsAt,
        rieSignals: row.rieSignals,
        weightsOverride: tunedWeights,
      });
      return { winner: decision.winnerPick, actual: row.actualWinner };
    }));

    const currentLiveTrain = summarize(train.map((row) => {
      const decision = computeHistoricalTeamPlusDecision({
        league: row.league,
        homeTeam: row.homeTeam,
        awayTeam: row.awayTeam,
        homeShort: row.homeShort,
        awayShort: row.awayShort,
        startsAt: row.startsAt,
        rieSignals: row.rieSignals,
        weightsOverride: TEAM_PLUS_WEIGHTS,
      });
      let winner = row.predictedWinner;
      if (row.league === 'mlb') {
        const override = buildMlbBaselineOverride({
          decision,
          baselineWinner: row.predictedWinner,
          homeTeam: row.homeTeam,
          awayTeam: row.awayTeam,
        });
        winner = override?.shouldOverride ? override.winnerPick : row.predictedWinner;
      } else {
        const threshold = CURRENT_LIVE_THRESHOLD[row.league];
        if (threshold != null && decision.confidence >= threshold) {
          winner = decision.winnerPick;
        }
      }
      return { winner, actual: row.actualWinner };
    }));

    const currentLiveTest = summarize(test.map((row) => {
      const decision = computeHistoricalTeamPlusDecision({
        league: row.league,
        homeTeam: row.homeTeam,
        awayTeam: row.awayTeam,
        homeShort: row.homeShort,
        awayShort: row.awayShort,
        startsAt: row.startsAt,
        rieSignals: row.rieSignals,
        weightsOverride: TEAM_PLUS_WEIGHTS,
      });
      let winner = row.predictedWinner;
      if (row.league === 'mlb') {
        const override = buildMlbBaselineOverride({
          decision,
          baselineWinner: row.predictedWinner,
          homeTeam: row.homeTeam,
          awayTeam: row.awayTeam,
        });
        winner = override?.shouldOverride ? override.winnerPick : row.predictedWinner;
      } else {
        const threshold = CURRENT_LIVE_THRESHOLD[row.league];
        if (threshold != null && decision.confidence >= threshold) {
          winner = decision.winnerPick;
        }
      }
      return { winner, actual: row.actualWinner };
    }));

    let bestHybrid: { threshold: number; train: Summary; test: Summary } | null = null;
    for (const threshold of [0.52, 0.54, 0.56, 0.58, 0.6, 0.62, 0.64, 0.66, 0.68, 0.7]) {
      const hybridTrain = summarize(train.map((row) => {
        const decision = computeHistoricalTeamPlusDecision({
          league: row.league,
          homeTeam: row.homeTeam,
          awayTeam: row.awayTeam,
          homeShort: row.homeShort,
          awayShort: row.awayShort,
          startsAt: row.startsAt,
          rieSignals: row.rieSignals,
          weightsOverride: tunedWeights,
        });
        const winner = decision.confidence >= threshold ? decision.winnerPick : row.predictedWinner;
        return { winner, actual: row.actualWinner };
      }));
      const hybridTest = summarize(test.map((row) => {
        const decision = computeHistoricalTeamPlusDecision({
          league: row.league,
          homeTeam: row.homeTeam,
          awayTeam: row.awayTeam,
          homeShort: row.homeShort,
          awayShort: row.awayShort,
          startsAt: row.startsAt,
          rieSignals: row.rieSignals,
          weightsOverride: tunedWeights,
        });
        const winner = decision.confidence >= threshold ? decision.winnerPick : row.predictedWinner;
        return { winner, actual: row.actualWinner };
      }));
      if (
        !bestHybrid
        || hybridTrain.wr > bestHybrid.train.wr
        || (hybridTrain.wr === bestHybrid.train.wr && hybridTest.wr > bestHybrid.test.wr)
      ) {
        bestHybrid = { threshold, train: hybridTrain, test: hybridTest };
      }
    }

    console.log(`\n=== ${league.toUpperCase()} TEAM PLUS BACKTEST ===`);
    console.log(`rows=${leagueRows.length} train=${train.length} test=${test.length}`);
    console.log(`baseline train=${(baselineTrain.wr * 100).toFixed(2)}% (${baselineTrain.wins}/${baselineTrain.picks})`);
    console.log(`baseline test=${(baselineTest.wr * 100).toFixed(2)}% (${baselineTest.wins}/${baselineTest.picks})`);
    console.log(`team_plus train=${(tunedTrain.wr * 100).toFixed(2)}% (${tunedTrain.wins}/${tunedTrain.picks})`);
    console.log(`team_plus test=${(tunedTest.wr * 100).toFixed(2)}% (${tunedTest.wins}/${tunedTest.picks})`);
    console.log(`current_live train=${(currentLiveTrain.wr * 100).toFixed(2)}% (${currentLiveTrain.wins}/${currentLiveTrain.picks})`);
    console.log(`current_live test=${(currentLiveTest.wr * 100).toFixed(2)}% (${currentLiveTest.wins}/${currentLiveTest.picks})`);
    if (bestHybrid) {
      console.log(`hybrid(threshold=${bestHybrid.threshold}) train=${(bestHybrid.train.wr * 100).toFixed(2)}% (${bestHybrid.train.wins}/${bestHybrid.train.picks})`);
      console.log(`hybrid(threshold=${bestHybrid.threshold}) test=${(bestHybrid.test.wr * 100).toFixed(2)}% (${bestHybrid.test.wins}/${bestHybrid.test.picks})`);
    }
    console.log(`weights=${JSON.stringify(tunedWeights[league])}`);
  }
}

main()
  .catch((error) => {
    console.error('[team-plus-backtest] failed', error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await pool.end();
  });
