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

type BacktestRow = {
  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;
};

type HybridSummary = Summary & {
  overrides: number;
};

type CandidateResult = {
  weights: TeamPlusWeightProfile;
  gate: MlbTeamPlusGateSettings;
  directTrain: Summary;
  directTest: Summary;
  hybridTrain: HybridSummary;
  hybridTest: HybridSummary;
};

type DecisionRow = {
  actualWinner: string;
  predictedWinner: string;
  decisionWinner: string;
  decisionConfidence: number;
  qualifies: (gate: MlbTeamPlusGateSettings) => boolean;
};

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* buildMlbWeightCandidates(): Generator<TeamPlusWeightProfile> {
  for (const matchup of [0.28, 0.32, 0.36, 0.40]) {
    for (const phase of [0.28, 0.32, 0.36, 0.40]) {
      for (const fangraphs of [0.08, 0.12, 0.16, 0.20]) {
        for (const piff of [0.04, 0.08, 0.12]) {
          const dvp = 1 - matchup - phase - fangraphs - piff;
          if (dvp < 0.04 || dvp > 0.14) 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,
            },
          };
        }
      }
    }
  }
}

function* buildGateCandidates(): Generator<MlbTeamPlusGateSettings> {
  for (const confidenceThreshold of [0.58, 0.60, 0.62, 0.64, 0.66]) {
    for (const requireStarterLinked of [true, false]) {
      for (const requireMatchupPhaseAgreement of [true, false]) {
        for (const requirePhaseInternalAgreement of [true, false]) {
          for (const requireFangraphsAgreement of [false, true]) {
            for (const minMatchupEdge of [0.04, 0.06, 0.08]) {
              for (const minPhaseEdge of [0.02, 0.04, 0.06]) {
                for (const minContextEdge of [0.00, 0.08, 0.12]) {
                  for (const minBullpenEdge of [0.00, 0.08, 0.12]) {
                    yield {
                      confidenceThreshold,
                      requireStarterLinked,
                      requireMatchupPhaseAgreement,
                      requirePhaseInternalAgreement,
                      requireFangraphsAgreement,
                      minMatchupEdge,
                      minPhaseEdge,
                      minContextEdge,
                      minBullpenEdge,
                    };
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

async function loadRows(): Promise<BacktestRow[]> {
  const { rows } = await pool.query(
    `SELECT 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 = 'mlb'
       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.resolved_at ASC`,
  );

  return rows
    .map((row: any) => ({
      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);
}

function evaluateHybrid(decisions: DecisionRow[], gate: MlbTeamPlusGateSettings): HybridSummary {
  let wins = 0;
  let overrides = 0;

  for (const row of decisions) {
    const useAlgo = row.qualifies(gate);
    const winner = useAlgo ? row.decisionWinner : row.predictedWinner;
    if (useAlgo) overrides += 1;
    if (winner === row.actualWinner) wins += 1;
  }

  return {
    picks: decisions.length,
    wins,
    wr: decisions.length > 0 ? wins / decisions.length : 0,
    overrides,
  };
}

function evaluateDirect(rows: BacktestRow[], weights: TeamPlusWeightProfile): Summary {
  return summarize(rows.map((row) => {
    const decision = computeHistoricalTeamPlusDecision({
      league: 'mlb',
      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,
    };
  }));
}

function materializeDecisions(rows: BacktestRow[], weights: TeamPlusWeightProfile): DecisionRow[] {
  return rows.map((row) => {
    const decision = computeHistoricalTeamPlusDecision({
      league: 'mlb',
      homeTeam: row.homeTeam,
      awayTeam: row.awayTeam,
      homeShort: row.homeShort,
      awayShort: row.awayShort,
      startsAt: row.startsAt,
      rieSignals: row.rieSignals,
      weightsOverride: weights,
    });

    return {
      actualWinner: row.actualWinner,
      predictedWinner: row.predictedWinner,
      decisionWinner: decision.winnerPick,
      decisionConfidence: decision.confidence,
      qualifies: (gate) => decision.confidence >= gate.confidenceThreshold && qualifiesForMlbTeamPlusLock(decision, gate),
    };
  });
}

function compareCandidate(left: CandidateResult | null, right: CandidateResult): CandidateResult {
  if (!left) return right;
  if (right.hybridTrain.wr !== left.hybridTrain.wr) {
    return right.hybridTrain.wr > left.hybridTrain.wr ? right : left;
  }
  if (right.hybridTrain.overrides !== left.hybridTrain.overrides) {
    return right.hybridTrain.overrides > left.hybridTrain.overrides ? right : left;
  }
  if (right.hybridTest.wr !== left.hybridTest.wr) {
    return right.hybridTest.wr > left.hybridTest.wr ? right : left;
  }
  return left;
}

async function main(): Promise<void> {
  const rows = await loadRows();
  const { train, test } = splitTrainTest(rows);
  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 bestDirectTrain: Summary | null = null;

  for (const weights of buildMlbWeightCandidates()) {
    const directTrain = evaluateDirect(train, weights);
    if (!bestDirectTrain || directTrain.wr > bestDirectTrain.wr) {
      bestDirectTrain = directTrain;
      bestWeights = weights;
    }
  }

  const tunedWeights = bestWeights || TEAM_PLUS_WEIGHTS;
  const directTrain = evaluateDirect(train, tunedWeights);
  const directTest = evaluateDirect(test, tunedWeights);
  const trainDecisions = materializeDecisions(train, tunedWeights);
  const testDecisions = materializeDecisions(test, tunedWeights);

  let bestCandidate: CandidateResult | null = null;
  for (const gate of buildGateCandidates()) {
    const hybridTrain = evaluateHybrid(trainDecisions, gate);
    if (hybridTrain.overrides < 8) continue;
    const hybridTest = evaluateHybrid(testDecisions, gate);
    const candidate: CandidateResult = {
      weights: tunedWeights,
      gate,
      directTrain,
      directTest,
      hybridTrain,
      hybridTest,
    };
    bestCandidate = compareCandidate(bestCandidate, candidate);
  }

  console.log('\n=== MLB TEAM PLUS OPTIMIZER ===');
  console.log(`rows=${rows.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(`direct train=${(directTrain.wr * 100).toFixed(2)}% (${directTrain.wins}/${directTrain.picks})`);
  console.log(`direct test=${(directTest.wr * 100).toFixed(2)}% (${directTest.wins}/${directTest.picks})`);
  console.log(`weights=${JSON.stringify(tunedWeights.mlb)}`);

  if (!bestCandidate) {
    console.log('hybrid=none');
    return;
  }

  console.log(
    `hybrid train=${(bestCandidate.hybridTrain.wr * 100).toFixed(2)}% `
    + `(${bestCandidate.hybridTrain.wins}/${bestCandidate.hybridTrain.picks}) `
    + `overrides=${bestCandidate.hybridTrain.overrides}`,
  );
  console.log(
    `hybrid test=${(bestCandidate.hybridTest.wr * 100).toFixed(2)}% `
    + `(${bestCandidate.hybridTest.wins}/${bestCandidate.hybridTest.picks}) `
    + `overrides=${bestCandidate.hybridTest.overrides}`,
  );
  console.log(`gate=${JSON.stringify(bestCandidate.gate)}`);
}

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