import 'dotenv/config';
import pool from '../db';
import {
  extractMlbDirectFeatures,
  MLB_DIRECT_MODEL_LIVE_CONFIG,
  MLB_DIRECT_MODEL_LIVE_POLICY,
  scoreMlbDirectModel,
  type MlbDirectPolicyConfig,
} from '../services/mlb-team-direct-model';
import type { SignalResult } from '../services/rie/types';

type Row = {
  eventId: string;
  homeTeam: string;
  awayTeam: string;
  predictedWinner: string;
  actualWinner: string;
  resolvedAt: string;
  rieSignals: SignalResult[];
  oddsData: any;
};

type Summary = {
  picks: number;
  wins: number;
  wr: number;
  pricedPicks: number;
  units: number;
  roi: number | null;
};

type PolicyRunSummary = {
  overall: Summary;
  selected: Summary;
  selectedCount: number;
  abstainCount: number;
  f5OnlyCount: number;
};

type OutcomeRow = {
  winner: string;
  actual: string;
  odds: number | null;
  selected: boolean;
};

type BucketSummary = Summary & {
  label: string;
};

type SegmentedPolicyCandidate = {
  favorite: MlbDirectPolicyConfig;
  dog: MlbDirectPolicyConfig;
  label: string;
};

type RollingWindowResult = {
  label: string;
  summary: PolicyRunSummary;
};

type RollingComparisonSummary = {
  windows: number;
  candidateBetterSelected: number;
  candidateBetterOverall: number;
  candidateNotWorseSelected: number;
  candidateNotWorseOverall: number;
  avgSelectedWrDelta: number;
  avgOverallWrDelta: number;
  worstSelectedWrDelta: number;
  worstOverallWrDelta: number;
  avgSelectedRoiDelta: number;
  avgOverallRoiDelta: number;
  worstSelectedRoiDelta: number;
  worstOverallRoiDelta: number;
};

function americanProfitPerUnit(odds: number | null | undefined): number | null {
  if (odds == null || !Number.isFinite(odds) || odds === 0) return null;
  return odds > 0 ? odds / 100 : 100 / Math.abs(odds);
}

function resolveMoneylineOdds(row: Row, winnerTeam: string): number | null {
  const moneyline = row.oddsData?.moneyline || {};
  if (winnerTeam === row.homeTeam) {
    const odds = Number(moneyline.home ?? null);
    return Number.isFinite(odds) ? odds : null;
  }
  if (winnerTeam === row.awayTeam) {
    const odds = Number(moneyline.away ?? null);
    return Number.isFinite(odds) ? odds : null;
  }
  return null;
}

function summarize(rows: Array<{ winner: string; actual: string; odds: number | null }>): Summary {
  const wins = rows.filter((row) => row.winner === row.actual).length;
  const picks = rows.length;
  const pricedRows = rows.filter((row) => row.odds != null);
  const units = pricedRows.reduce((sum, row) => {
    const profit = americanProfitPerUnit(row.odds);
    if (profit == null) return sum;
    return sum + (row.winner === row.actual ? profit : -1);
  }, 0);
  return {
    picks,
    wins,
    wr: picks > 0 ? wins / picks : 0,
    pricedPicks: pricedRows.length,
    units,
    roi: pricedRows.length > 0 ? units / pricedRows.length : null,
  };
}

function sideBucket(odds: number | null): string {
  if (odds == null) return 'unknown';
  return odds < 0 ? 'favorite' : 'dog';
}

function priceBand(odds: number | null): string {
  if (odds == null) return 'unknown';
  if (odds <= -180) return 'fav_heavy';
  if (odds <= -130) return 'fav_mid';
  if (odds < 130) return 'coinflip';
  if (odds < 180) return 'dog_mid';
  return 'dog_big';
}

function summarizeBuckets(rows: OutcomeRow[], bucketFn: (odds: number | null) => string): BucketSummary[] {
  const groups = new Map<string, Array<{ winner: string; actual: string; odds: number | null }>>();
  for (const row of rows) {
    const label = bucketFn(row.odds);
    const bucket = groups.get(label) || [];
    bucket.push({ winner: row.winner, actual: row.actual, odds: row.odds });
    groups.set(label, bucket);
  }
  return [...groups.entries()]
    .map(([label, bucketRows]) => ({ label, ...summarize(bucketRows) }))
    .sort((a, b) => a.label.localeCompare(b.label));
}

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 compareRuns(left: PolicyRunSummary, right: PolicyRunSummary): number {
  const leftSelectedRoi = left.selected.roi ?? -999;
  const rightSelectedRoi = right.selected.roi ?? -999;
  if (leftSelectedRoi !== rightSelectedRoi) return leftSelectedRoi - rightSelectedRoi;
  if (left.selected.wr !== right.selected.wr) return left.selected.wr - right.selected.wr;
  const leftOverallRoi = left.overall.roi ?? -999;
  const rightOverallRoi = right.overall.roi ?? -999;
  if (leftOverallRoi !== rightOverallRoi) return leftOverallRoi - rightOverallRoi;
  if (left.overall.wr !== right.overall.wr) return left.overall.wr - right.overall.wr;
  if (left.selectedCount !== right.selectedCount) return left.selectedCount - right.selectedCount;
  return right.abstainCount - left.abstainCount;
}

function* buildCalibrationPolicyCandidates(): Generator<MlbDirectPolicyConfig> {
  const interactionRanges: Array<[number | null, number | null]> = [
    [null, null],
    [0.02, 0.08],
    [0.02, 0.1],
    [0.03, 0.1],
    [0.03, 0.12],
    [0.04, 0.12],
    [0.04, 0.14],
  ];

  for (const [starterInteractionWeakPositiveMin, starterInteractionWeakPositiveMax] of interactionRanges) {
    for (const decompositionPositiveBlockMin of [null, 0.1, 0.12, 0.14, 0.16]) {
      for (const strongMarketOverrideMin of [null, 0.07, 0.08, 0.1]) {
        for (const favoritePriceBlockMax of [null, -180, -160]) {
          for (const favoritePriceRequireMarketEdgeMin of [null, 0.07]) {
            for (const minConfidence of [0.5, 0.51, 0.52]) {
              for (const minFeatureCoverageScore of [0.375, 0.5]) {
                for (const maxConflictCount of [3, 4]) {
                  yield {
                    ...MLB_DIRECT_MODEL_LIVE_POLICY,
                    minConfidence,
                    minFeatureCoverageScore,
                    maxConflictCount,
                    starterInteractionWeakPositiveMin,
                    starterInteractionWeakPositiveMax,
                    decompositionPositiveBlockMin,
                    strongMarketOverrideMin,
                    favoritePriceBlockMax,
                    favoritePriceRequireMarketEdgeMin,
                  };
                }
              }
            }
          }
        }
      }
    }
  }
}

function buildSegmentedPolicyCandidates(): SegmentedPolicyCandidate[] {
  const favoriteCandidates: MlbDirectPolicyConfig[] = [
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: null,
      favoritePriceBlockMax: null,
      favoritePriceRequireMarketEdgeMin: null,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -180,
      favoritePriceRequireMarketEdgeMin: 0.07,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      favoriteMarketSupportLeanMin: 0.03,
      favoriteMarketSupportLeanMax: 0.1,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      favoriteStarterSupportStrongMin: 0.1,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      favoriteDecompositionSupportStrongMin: 0.1,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      favoriteContactSupportStrongMin: 0.1,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: null,
      favoriteMarketSupportLeanMin: 0.03,
      favoriteMarketSupportLeanMax: 0.1,
      favoriteStarterSupportStrongMin: 0.1,
      favoriteDecompositionSupportStrongMin: 0.1,
      favoriteContactSupportStrongMin: 0.1,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      strongMarketOverrideMin: 0.1,
      favoritePriceBlockMax: -160,
      favoritePriceRequireMarketEdgeMin: 0.07,
      starterInteractionWeakPositiveMin: 0.03,
      starterInteractionWeakPositiveMax: 0.1,
      decompositionPositiveBlockMin: 0.12,
      maxConflictCount: 3,
    },
  ];

  const dogCandidates: MlbDirectPolicyConfig[] = [
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      favoritePriceBlockMax: null,
      favoritePriceRequireMarketEdgeMin: null,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 3,
      strongMarketOverrideMin: null,
      favoritePriceBlockMax: null,
      favoritePriceRequireMarketEdgeMin: null,
    },
    {
      ...MLB_DIRECT_MODEL_LIVE_POLICY,
      starterInteractionWeakPositiveMin: null,
      starterInteractionWeakPositiveMax: null,
      decompositionPositiveBlockMin: null,
      maxConflictCount: 4,
      minConfidence: 0.5,
      strongMarketOverrideMin: 0.08,
      favoritePriceBlockMax: null,
      favoritePriceRequireMarketEdgeMin: null,
    },
  ];

  const candidates: SegmentedPolicyCandidate[] = [];
  favoriteCandidates.forEach((favorite, favoriteIndex) => {
    dogCandidates.forEach((dog, dogIndex) => {
      candidates.push({
        favorite,
        dog,
        label: `fav${favoriteIndex + 1}_dog${dogIndex + 1}`,
      });
    });
  });
  return candidates;
}

async function loadRows(): Promise<Row[]> {
  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.odds_data,
            fc.model_signals
       FROM rm_forecast_accuracy_v2 fa
       JOIN rm_forecast_cache fc ON fc.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,
    predictedWinner: row.predicted_winner,
    actualWinner: row.actual_winner,
    resolvedAt: row.resolved_at,
    rieSignals: Array.isArray(row.model_signals?.rieSignals) ? row.model_signals.rieSignals : [],
    oddsData: row.odds_data || {},
  }));
}

function buildRollingWindows(rows: Row[], windowSize = 36, step = 18): Row[][] {
  if (rows.length <= windowSize) return [rows];
  const windows: Row[][] = [];
  for (let start = 0; start + windowSize <= rows.length; start += step) {
    windows.push(rows.slice(start, start + windowSize));
  }
  const lastWindow = windows[windows.length - 1];
  if (!lastWindow || lastWindow[lastWindow.length - 1] !== rows[rows.length - 1]) {
    windows.push(rows.slice(Math.max(0, rows.length - windowSize)));
  }
  return windows;
}

function average(values: number[]): number {
  return values.length ? values.reduce((sum, value) => sum + value, 0) / values.length : 0;
}

function dateKey(value: string | Date): string {
  if (value instanceof Date) return value.toISOString().slice(0, 10);
  if (typeof value === 'string') return value.slice(0, 10);
  return 'na';
}

function summarizeRollingWindows(rows: Row[], policy: MlbDirectPolicyConfig, windowSize = 36, step = 18): RollingWindowResult[] {
  return buildRollingWindows(rows, windowSize, step).map((windowRows, index) => ({
    label: `${index + 1}:${dateKey(windowRows[0]?.resolvedAt)}->${dateKey(windowRows[windowRows.length - 1]?.resolvedAt)}`,
    summary: summarizePolicyRun(windowRows, policy),
  }));
}

function compareRolling(candidate: RollingWindowResult[], baseline: RollingWindowResult[]): RollingComparisonSummary {
  const length = Math.min(candidate.length, baseline.length);
  const selectedDeltas: number[] = [];
  const overallDeltas: number[] = [];
  const selectedRoiDeltas: number[] = [];
  const overallRoiDeltas: number[] = [];
  let candidateBetterSelected = 0;
  let candidateBetterOverall = 0;
  let candidateNotWorseSelected = 0;
  let candidateNotWorseOverall = 0;

  for (let i = 0; i < length; i += 1) {
    const selectedDelta = candidate[i].summary.selected.wr - baseline[i].summary.selected.wr;
    const overallDelta = candidate[i].summary.overall.wr - baseline[i].summary.overall.wr;
    const selectedRoiDelta = (candidate[i].summary.selected.roi ?? 0) - (baseline[i].summary.selected.roi ?? 0);
    const overallRoiDelta = (candidate[i].summary.overall.roi ?? 0) - (baseline[i].summary.overall.roi ?? 0);
    selectedDeltas.push(selectedDelta);
    overallDeltas.push(overallDelta);
    selectedRoiDeltas.push(selectedRoiDelta);
    overallRoiDeltas.push(overallRoiDelta);
    if (selectedDelta > 0) candidateBetterSelected += 1;
    if (overallDelta > 0) candidateBetterOverall += 1;
    if (selectedDelta >= 0) candidateNotWorseSelected += 1;
    if (overallDelta >= 0) candidateNotWorseOverall += 1;
  }

  return {
    windows: length,
    candidateBetterSelected,
    candidateBetterOverall,
    candidateNotWorseSelected,
    candidateNotWorseOverall,
    avgSelectedWrDelta: average(selectedDeltas),
    avgOverallWrDelta: average(overallDeltas),
    worstSelectedWrDelta: selectedDeltas.length ? Math.min(...selectedDeltas) : 0,
    worstOverallWrDelta: overallDeltas.length ? Math.min(...overallDeltas) : 0,
    avgSelectedRoiDelta: average(selectedRoiDeltas),
    avgOverallRoiDelta: average(overallRoiDeltas),
    worstSelectedRoiDelta: selectedRoiDeltas.length ? Math.min(...selectedRoiDeltas) : 0,
    worstOverallRoiDelta: overallRoiDeltas.length ? Math.min(...overallRoiDeltas) : 0,
  };
}

function collectPolicyOutcomeRows(rows: Row[], policy: MlbDirectPolicyConfig): {
  outcomes: OutcomeRow[];
  abstainCount: number;
  f5OnlyCount: number;
} {
  const outcomes: OutcomeRow[] = [];
  let abstainCount = 0;
  let f5OnlyCount = 0;

  for (const row of rows) {
    const featureVector = extractMlbDirectFeatures({
      signals: row.rieSignals,
      oddsData: row.oddsData,
    });
    const decision = scoreMlbDirectModel({
      homeTeam: row.homeTeam,
      awayTeam: row.awayTeam,
      featureVector,
      config: MLB_DIRECT_MODEL_LIVE_CONFIG,
      policy,
    });

    if (decision.available && decision.policy.shouldBet && decision.policy.recommendedMarket === 'moneyline') {
      outcomes.push({
        winner: decision.winnerPick,
        actual: row.actualWinner,
        odds: resolveMoneylineOdds(row, decision.winnerPick),
        selected: true,
      });
    } else {
      outcomes.push({
        winner: row.predictedWinner,
        actual: row.actualWinner,
        odds: resolveMoneylineOdds(row, row.predictedWinner),
        selected: false,
      });
      if (decision.policy.recommendedMarket === 'f5_moneyline') {
        f5OnlyCount += 1;
      } else {
        abstainCount += 1;
      }
    }
  }

  return { outcomes, abstainCount, f5OnlyCount };
}

function collectSegmentedOutcomeRows(rows: Row[], candidate: SegmentedPolicyCandidate): {
  outcomes: OutcomeRow[];
  abstainCount: number;
  f5OnlyCount: number;
} {
  const outcomes: OutcomeRow[] = [];
  let abstainCount = 0;
  let f5OnlyCount = 0;

  for (const row of rows) {
    const featureVector = extractMlbDirectFeatures({
      signals: row.rieSignals,
      oddsData: row.oddsData,
    });
    const baseDecision = scoreMlbDirectModel({
      homeTeam: row.homeTeam,
      awayTeam: row.awayTeam,
      featureVector,
      config: MLB_DIRECT_MODEL_LIVE_CONFIG,
      policy: MLB_DIRECT_MODEL_LIVE_POLICY,
    });
    const pickedOdds = resolveMoneylineOdds(row, baseDecision.winnerPick);
    const selectedPolicy = pickedOdds != null && pickedOdds < 0 ? candidate.favorite : candidate.dog;
    const decision = scoreMlbDirectModel({
      homeTeam: row.homeTeam,
      awayTeam: row.awayTeam,
      featureVector,
      config: MLB_DIRECT_MODEL_LIVE_CONFIG,
      policy: selectedPolicy,
    });

    if (decision.available && decision.policy.shouldBet && decision.policy.recommendedMarket === 'moneyline') {
      outcomes.push({
        winner: decision.winnerPick,
        actual: row.actualWinner,
        odds: resolveMoneylineOdds(row, decision.winnerPick),
        selected: true,
      });
    } else {
      outcomes.push({
        winner: row.predictedWinner,
        actual: row.actualWinner,
        odds: resolveMoneylineOdds(row, row.predictedWinner),
        selected: false,
      });
      if (decision.policy.recommendedMarket === 'f5_moneyline') {
        f5OnlyCount += 1;
      } else {
        abstainCount += 1;
      }
    }
  }

  return { outcomes, abstainCount, f5OnlyCount };
}

function summarizeSegmentedPolicyRun(rows: Row[], candidate: SegmentedPolicyCandidate): PolicyRunSummary {
  const result = collectSegmentedOutcomeRows(rows, candidate);
  const selectedRows = result.outcomes.filter((row) => row.selected);
  return {
    overall: summarize(result.outcomes),
    selected: summarize(selectedRows),
    selectedCount: selectedRows.length,
    abstainCount: result.abstainCount,
    f5OnlyCount: result.f5OnlyCount,
  };
}

function summarizePolicyRun(rows: Row[], policy: MlbDirectPolicyConfig): PolicyRunSummary {
  const result = collectPolicyOutcomeRows(rows, policy);
  const selectedRows = result.outcomes.filter((row) => row.selected);
  return {
    overall: summarize(result.outcomes),
    selected: summarize(selectedRows),
    selectedCount: selectedRows.length,
    abstainCount: result.abstainCount,
    f5OnlyCount: result.f5OnlyCount,
  };
}

async function main(): Promise<void> {
  const rows = await loadRows();
  const { train: development, test } = splitTrainTest(rows);
  const { train, test: validation } = splitTrainTest(development);

  const liveTrain = summarizePolicyRun(train, MLB_DIRECT_MODEL_LIVE_POLICY);
  const liveValidation = summarizePolicyRun(validation, MLB_DIRECT_MODEL_LIVE_POLICY);
  const liveTest = summarizePolicyRun(test, MLB_DIRECT_MODEL_LIVE_POLICY);

  const minValidationFires = Math.max(18, Math.floor(liveValidation.selectedCount * 0.65));
  const minOverallWr = liveValidation.overall.wr - 0.01;
  const liveRolling = summarizeRollingWindows(development, MLB_DIRECT_MODEL_LIVE_POLICY);

  let bestPolicy: MlbDirectPolicyConfig | null = null;
  let bestTrain: PolicyRunSummary | null = null;
  let bestValidation: PolicyRunSummary | null = null;
  let bestTest: PolicyRunSummary | null = null;
  let bestRolling: RollingComparisonSummary | null = null;
  let bestSegmentedCandidate: SegmentedPolicyCandidate | null = null;
  let bestSegmentedTrain: PolicyRunSummary | null = null;
  let bestSegmentedValidation: PolicyRunSummary | null = null;
  let bestSegmentedTest: PolicyRunSummary | null = null;
  let bestSegmentedRolling: RollingComparisonSummary | null = null;

  for (const policy of buildCalibrationPolicyCandidates()) {
    const trainSummary = summarizePolicyRun(train, policy);
    const validationSummary = summarizePolicyRun(validation, policy);
    if (validationSummary.selectedCount < minValidationFires) continue;
    if (validationSummary.overall.wr < minOverallWr) continue;
    const rollingSummary = compareRolling(summarizeRollingWindows(development, policy), liveRolling);

    const shouldReplace =
      !bestPolicy
      || !bestValidation
      || compareRuns(validationSummary, bestValidation) > 0
      || (
        compareRuns(validationSummary, bestValidation) === 0
        && bestRolling
        && (
          rollingSummary.candidateNotWorseSelected > bestRolling.candidateNotWorseSelected
          || (
            rollingSummary.candidateNotWorseSelected === bestRolling.candidateNotWorseSelected
            && (
              rollingSummary.avgSelectedWrDelta > bestRolling.avgSelectedWrDelta
              || (
                rollingSummary.avgSelectedWrDelta === bestRolling.avgSelectedWrDelta
                && bestTrain
                && (
                  Math.abs(trainSummary.selected.wr - validationSummary.selected.wr)
                    < Math.abs(bestTrain.selected.wr - bestValidation.selected.wr)
                  || (
                    Math.abs(trainSummary.selected.wr - validationSummary.selected.wr)
                      === Math.abs(bestTrain.selected.wr - bestValidation.selected.wr)
                    && trainSummary.selectedCount > bestTrain.selectedCount
                  )
                )
              )
            )
          )
        )
      );

    if (shouldReplace) {
      bestPolicy = policy;
      bestTrain = trainSummary;
      bestValidation = validationSummary;
      bestTest = summarizePolicyRun(test, policy);
      bestRolling = rollingSummary;
    }
  }

  for (const candidate of buildSegmentedPolicyCandidates()) {
    const trainSummary = summarizeSegmentedPolicyRun(train, candidate);
    const validationSummary = summarizeSegmentedPolicyRun(validation, candidate);
    if (validationSummary.selectedCount < minValidationFires) continue;
    if ((validationSummary.overall.roi ?? -999) < (liveValidation.overall.roi ?? -999) - 0.03) continue;
    const segmentedRolling = compareRolling(
      buildRollingWindows(development, 36, 18).map((windowRows, index) => ({
        label: `${index + 1}`,
        summary: summarizeSegmentedPolicyRun(windowRows, candidate),
      })),
      liveRolling,
    );

    const shouldReplace =
      !bestSegmentedCandidate
      || !bestSegmentedValidation
      || compareRuns(validationSummary, bestSegmentedValidation) > 0
      || (
        compareRuns(validationSummary, bestSegmentedValidation) === 0
        && bestSegmentedRolling
        && (
          segmentedRolling.avgSelectedRoiDelta > bestSegmentedRolling.avgSelectedRoiDelta
          || (
            segmentedRolling.avgSelectedRoiDelta === bestSegmentedRolling.avgSelectedRoiDelta
            && segmentedRolling.candidateNotWorseSelected > bestSegmentedRolling.candidateNotWorseSelected
          )
        )
      );

    if (shouldReplace) {
      bestSegmentedCandidate = candidate;
      bestSegmentedTrain = trainSummary;
      bestSegmentedValidation = validationSummary;
      bestSegmentedTest = summarizeSegmentedPolicyRun(test, candidate);
      bestSegmentedRolling = segmentedRolling;
    }
  }

  console.log('=== MLB TEAM POLICY OPTIMIZER ===');
  console.log(`rows=${rows.length} train=${train.length} validation=${validation.length} test=${test.length}`);
  console.log(`mode=cache_signals_only`);
  console.log(`live train=${(liveTrain.overall.wr * 100).toFixed(2)}% (${liveTrain.overall.wins}/${liveTrain.overall.picks}) roi=${liveTrain.overall.roi != null ? (liveTrain.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${liveTrain.selectedCount}`);
  console.log(`live validation=${(liveValidation.overall.wr * 100).toFixed(2)}% (${liveValidation.overall.wins}/${liveValidation.overall.picks}) roi=${liveValidation.overall.roi != null ? (liveValidation.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${liveValidation.selectedCount}`);
  console.log(`live validation selected=${(liveValidation.selected.wr * 100).toFixed(2)}% (${liveValidation.selected.wins}/${liveValidation.selected.picks}) roi=${liveValidation.selected.roi != null ? (liveValidation.selected.roi * 100).toFixed(2) + '%' : 'na'}`);
  console.log(`live test=${(liveTest.overall.wr * 100).toFixed(2)}% (${liveTest.overall.wins}/${liveTest.overall.picks}) roi=${liveTest.overall.roi != null ? (liveTest.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${liveTest.selectedCount} abstain=${liveTest.abstainCount}`);
  console.log(`live test selected=${(liveTest.selected.wr * 100).toFixed(2)}% (${liveTest.selected.wins}/${liveTest.selected.picks}) roi=${liveTest.selected.roi != null ? (liveTest.selected.roi * 100).toFixed(2) + '%' : 'na'}`);
  const liveRollingSummary = compareRolling(liveRolling, liveRolling);
  console.log(`live rolling windows=${liveRollingSummary.windows}`);

  if (!bestPolicy || !bestTrain || !bestValidation || !bestTest) {
    console.log('best=none');
    return;
  }

  console.log(`best train=${(bestTrain.overall.wr * 100).toFixed(2)}% (${bestTrain.overall.wins}/${bestTrain.overall.picks}) roi=${bestTrain.overall.roi != null ? (bestTrain.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${bestTrain.selectedCount}`);
  console.log(`best validation=${(bestValidation.overall.wr * 100).toFixed(2)}% (${bestValidation.overall.wins}/${bestValidation.overall.picks}) roi=${bestValidation.overall.roi != null ? (bestValidation.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${bestValidation.selectedCount}`);
  console.log(`best validation selected=${(bestValidation.selected.wr * 100).toFixed(2)}% (${bestValidation.selected.wins}/${bestValidation.selected.picks}) roi=${bestValidation.selected.roi != null ? (bestValidation.selected.roi * 100).toFixed(2) + '%' : 'na'}`);
  console.log(`best test=${(bestTest.overall.wr * 100).toFixed(2)}% (${bestTest.overall.wins}/${bestTest.overall.picks}) roi=${bestTest.overall.roi != null ? (bestTest.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${bestTest.selectedCount} abstain=${bestTest.abstainCount}`);
  console.log(`best test selected=${(bestTest.selected.wr * 100).toFixed(2)}% (${bestTest.selected.wins}/${bestTest.selected.picks}) roi=${bestTest.selected.roi != null ? (bestTest.selected.roi * 100).toFixed(2) + '%' : 'na'}`);
  if (bestRolling) {
    console.log(`best rolling better_selected=${bestRolling.candidateBetterSelected}/${bestRolling.windows} not_worse_selected=${bestRolling.candidateNotWorseSelected}/${bestRolling.windows} avg_selected_delta=${(bestRolling.avgSelectedWrDelta * 100).toFixed(2)} worst_selected_delta=${(bestRolling.worstSelectedWrDelta * 100).toFixed(2)} avg_selected_roi_delta=${(bestRolling.avgSelectedRoiDelta * 100).toFixed(2)} worst_selected_roi_delta=${(bestRolling.worstSelectedRoiDelta * 100).toFixed(2)} avg_overall_delta=${(bestRolling.avgOverallWrDelta * 100).toFixed(2)} worst_overall_delta=${(bestRolling.worstOverallWrDelta * 100).toFixed(2)} avg_overall_roi_delta=${(bestRolling.avgOverallRoiDelta * 100).toFixed(2)} worst_overall_roi_delta=${(bestRolling.worstOverallRoiDelta * 100).toFixed(2)}`);
  }

  const liveTestOutcomes = collectPolicyOutcomeRows(test, MLB_DIRECT_MODEL_LIVE_POLICY).outcomes.filter((row) => row.selected);
  const bestTestOutcomes = collectPolicyOutcomeRows(test, bestPolicy).outcomes.filter((row) => row.selected);
  const bestSegmentedTestOutcomes = bestSegmentedCandidate
    ? collectSegmentedOutcomeRows(test, bestSegmentedCandidate).outcomes.filter((row) => row.selected)
    : [];
  const printBuckets = (label: string, buckets: BucketSummary[]) => {
    console.log(label);
    for (const bucket of buckets) {
      console.log(`  ${bucket.label}: picks=${bucket.picks} wr=${(bucket.wr * 100).toFixed(2)}% units=${bucket.units.toFixed(2)} roi=${bucket.roi != null ? (bucket.roi * 100).toFixed(2) + '%' : 'na'}`);
    }
  };
  printBuckets('live_test_selected_side_buckets=', summarizeBuckets(liveTestOutcomes, (odds) => sideBucket(odds)));
  printBuckets('live_test_selected_price_bands=', summarizeBuckets(liveTestOutcomes, (odds) => priceBand(odds)));
  printBuckets('best_test_selected_side_buckets=', summarizeBuckets(bestTestOutcomes, (odds) => sideBucket(odds)));
  printBuckets('best_test_selected_price_bands=', summarizeBuckets(bestTestOutcomes, (odds) => priceBand(odds)));
  if (bestSegmentedCandidate && bestSegmentedTrain && bestSegmentedValidation && bestSegmentedTest) {
    console.log(`best_segmented label=${bestSegmentedCandidate.label}`);
    console.log(`best_segmented test=${(bestSegmentedTest.overall.wr * 100).toFixed(2)}% (${bestSegmentedTest.overall.wins}/${bestSegmentedTest.overall.picks}) roi=${bestSegmentedTest.overall.roi != null ? (bestSegmentedTest.overall.roi * 100).toFixed(2) + '%' : 'na'} fires=${bestSegmentedTest.selectedCount} abstain=${bestSegmentedTest.abstainCount}`);
    console.log(`best_segmented test selected=${(bestSegmentedTest.selected.wr * 100).toFixed(2)}% (${bestSegmentedTest.selected.wins}/${bestSegmentedTest.selected.picks}) roi=${bestSegmentedTest.selected.roi != null ? (bestSegmentedTest.selected.roi * 100).toFixed(2) + '%' : 'na'}`);
    if (bestSegmentedRolling) {
      console.log(`best_segmented rolling avg_selected_roi_delta=${(bestSegmentedRolling.avgSelectedRoiDelta * 100).toFixed(2)} not_worse_selected=${bestSegmentedRolling.candidateNotWorseSelected}/${bestSegmentedRolling.windows}`);
    }
    printBuckets('best_segmented_test_selected_side_buckets=', summarizeBuckets(bestSegmentedTestOutcomes, (odds) => sideBucket(odds)));
    printBuckets('best_segmented_test_selected_price_bands=', summarizeBuckets(bestSegmentedTestOutcomes, (odds) => priceBand(odds)));
    console.log(`segmented_favorite_policy=${JSON.stringify(bestSegmentedCandidate.favorite)}`);
    console.log(`segmented_dog_policy=${JSON.stringify(bestSegmentedCandidate.dog)}`);
  }
  console.log(`policy=${JSON.stringify(bestPolicy)}`);
}

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