import dotenv from 'dotenv';
import path from 'path';
dotenv.config({ path: path.join(__dirname, '../../.env') });

import pool from '../db';
import { gradeFromForecastData, type BenchmarkGrade } from '../services/benchmark-grading';

function sameNullableNumber(a: unknown, b: unknown): boolean {
  if (a == null && b == null) return true;
  if (a == null || b == null) return false;
  const left = Number(a);
  const right = Number(b);
  if (!Number.isFinite(left) || !Number.isFinite(right)) return false;
  return Math.abs(left - right) < 1e-9;
}

function sameText(a: unknown, b: unknown): boolean {
  return String(a ?? '') === String(b ?? '');
}

function buildWinnerScore(actualWinner: string | null, homeScore: number | null, awayScore: number | null): string | null {
  if (homeScore == null || awayScore == null) return null;
  if (homeScore === awayScore) return `${homeScore}-${awayScore}`;
  if (!actualWinner || actualWinner === 'DRAW') return `${homeScore}-${awayScore}`;
  return `${actualWinner} ${Math.max(homeScore, awayScore)}`;
}

function toArchiveOutcome(grade: BenchmarkGrade): 'win' | 'loss' | 'push' {
  if (grade === 'W') return 'win';
  if (grade === 'P') return 'push';
  return 'loss';
}

export async function repairSpreadIntegrity(options: { closePool?: boolean } = {}): Promise<{
  audited: number;
  repaired: number;
  repairedGrades: number;
  repairedArchives: number;
  skipped: number;
}> {
  const { closePool = false } = options;
  console.log(`[spread-repair] Starting at ${new Date().toISOString()}`);

  const { rows } = await pool.query(`
    SELECT
      fa.id,
      fa.forecast_id,
      fa.event_id,
      fa.league,
      fa.event_date,
      fa.final_grade,
      fa.grading_policy,
      fa.forecast_type,
      fa.original_forecast,
      fa.benchmark_forecast,
      fa.closing_market,
      fa.benchmark_source,
      fa.actual_winner,
      fa.home_score,
      fa.away_score,
      af.id AS archive_id,
      af.home_team AS archive_home_team,
      af.away_team AS archive_away_team,
      af.forecast_data AS archive_forecast_data,
      af.odds_data AS archive_odds_data,
      af.outcome AS archive_outcome,
      af.actual_winner AS archive_actual_winner,
      af.actual_score AS archive_actual_score,
      fc.home_team AS cache_home_team,
      fc.away_team AS cache_away_team,
      fc.forecast_data AS cache_forecast_data,
      fc.odds_data AS cache_odds_data
    FROM rm_forecast_accuracy_v2 fa
    LEFT JOIN rm_archived_forecasts af ON af.forecast_id = fa.forecast_id
    LEFT JOIN rm_forecast_cache fc ON fc.id = fa.forecast_id
    WHERE fa.final_grade IS NOT NULL
      AND COALESCE(fa.forecast_type, 'spread') = 'spread'
    ORDER BY fa.event_date DESC NULLS LAST, fa.event_id ASC
  `);

  let repaired = 0;
  let repairedGrades = 0;
  let repairedArchives = 0;
  let skipped = 0;

  for (const row of rows) {
    const homeTeam = row.archive_home_team || row.cache_home_team;
    const awayTeam = row.archive_away_team || row.cache_away_team;
    const forecastData = row.archive_forecast_data || row.cache_forecast_data;
    const oddsData = row.archive_odds_data || row.cache_odds_data;
    const homeScore = row.home_score != null ? Number(row.home_score) : null;
    const awayScore = row.away_score != null ? Number(row.away_score) : null;

    if (!homeTeam || !awayTeam || !forecastData || homeScore == null || awayScore == null) {
      skipped++;
      continue;
    }

    const recomputed = gradeFromForecastData(
      forecastData,
      oddsData,
      homeTeam,
      awayTeam,
      homeScore,
      awayScore,
      row.league,
    );

    if (!recomputed) {
      skipped++;
      continue;
    }

    const actualWinner = homeScore === awayScore
      ? 'DRAW'
      : (homeScore > awayScore ? homeTeam : awayTeam);
    const actualSpread = homeScore - awayScore;
    const actualTotal = homeScore + awayScore;
    const winnerScore = buildWinnerScore(actualWinner, homeScore, awayScore);

    const gradeChanged =
      !sameText(row.final_grade, recomputed.grade) ||
      !sameText(row.grading_policy, recomputed.gradingPolicy) ||
      !sameText(row.forecast_type, recomputed.forecastType) ||
      !sameNullableNumber(row.original_forecast, recomputed.originalForecast) ||
      !sameNullableNumber(row.benchmark_forecast, recomputed.benchmarkForecast) ||
      !sameNullableNumber(row.closing_market, recomputed.closingMarket) ||
      !sameText(row.benchmark_source, recomputed.benchmarkSource) ||
      !sameText(row.actual_winner, actualWinner);

    if (gradeChanged) {
      await pool.query(
        `UPDATE rm_forecast_accuracy_v2
         SET actual_spread = $2::double precision,
             actual_total = $3::double precision,
             actual_winner = $4,
             winner_score = $5,
             final_grade = $6,
             grading_policy = $7,
             forecast_type = $8,
             original_forecast = $9,
             benchmark_forecast = $10,
             closing_market = $11,
             benchmark_source = $12
         WHERE id = $1`,
        [
          row.id,
          actualSpread,
          actualTotal,
          actualWinner,
          winnerScore,
          recomputed.grade,
          recomputed.gradingPolicy,
          recomputed.forecastType,
          recomputed.originalForecast,
          recomputed.benchmarkForecast,
          recomputed.closingMarket,
          recomputed.benchmarkSource,
        ],
      );
      repaired++;
      repairedGrades++;
    }

    if (row.archive_id) {
      const archiveOutcome = toArchiveOutcome(recomputed.grade);
      const archiveScore = `${homeScore}-${awayScore}`;
      const archiveChanged =
        !sameText(row.archive_outcome, archiveOutcome) ||
        !sameText(row.archive_actual_winner, actualWinner) ||
        !sameText(row.archive_actual_score, archiveScore);

      if (archiveChanged) {
        await pool.query(
          `UPDATE rm_archived_forecasts
           SET outcome = $2,
               actual_winner = $3,
               actual_score = $4,
               settled_at = COALESCE(settled_at, NOW())
           WHERE id = $1`,
          [row.archive_id, archiveOutcome, actualWinner, archiveScore],
        );
        repairedArchives++;
      }
    }
  }

  const summary = {
    audited: rows.length,
    repaired,
    repairedGrades,
    repairedArchives,
    skipped,
  };
  console.log(`[spread-repair] ${JSON.stringify(summary)}`);

  if (closePool) {
    await pool.end().catch(() => undefined);
  }

  return summary;
}

async function main() {
  await repairSpreadIntegrity({ closePool: true });
}

if (require.main === module) {
  main().catch((err) => {
    console.error('[spread-repair] Fatal error:', err);
    process.exit(1);
  });
}
