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

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

const ESPN_LEAGUES: Record<string, { sport: string; league: string; groups?: string }> = {
  nba: { sport: 'basketball', league: 'nba' },
  nhl: { sport: 'hockey', league: 'nhl' },
  mlb: { sport: 'baseball', league: 'mlb' },
  ncaab: { sport: 'basketball', league: 'mens-college-basketball', groups: '50' },
  ncaaf: { sport: 'football', league: 'college-football' },
  nfl: { sport: 'football', league: 'nfl' },
  wnba: { sport: 'basketball', league: 'wnba' },
  epl: { sport: 'soccer', league: 'eng.1' },
  la_liga: { sport: 'soccer', league: 'esp.1' },
  serie_a: { sport: 'soccer', league: 'ita.1' },
  bundesliga: { sport: 'soccer', league: 'ger.1' },
  ligue_1: { sport: 'soccer', league: 'fra.1' },
  mls: { sport: 'soccer', league: 'usa.1' },
  champions_league: { sport: 'soccer', league: 'uefa.champions' },
};

interface ESPNScore {
  homeTeam: string;
  awayTeam: string;
  homeScore: number;
  awayScore: number;
}

function parseEspnScoreValue(value: unknown): number | null {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value);
    if (Number.isFinite(parsed)) return parsed;
  }
  return null;
}

function getEspnCompetitorName(competitor: any): string {
  return competitor?.team?.displayName
    || competitor?.team?.name
    || competitor?.athlete?.displayName
    || competitor?.athlete?.fullName
    || competitor?.athlete?.shortName
    || '';
}

function getEspnCompetitorScore(competitor: any): number | null {
  const direct = parseEspnScoreValue(competitor?.score);
  if (direct != null) return direct;
  return parseEspnScoreValue(competitor?.linescores?.[0]?.value);
}

async function fetchESPNScores(sport: string, league: string, dateStr: string, groups?: string): Promise<ESPNScore[]> {
  let url = `https://site.api.espn.com/apis/site/v2/sports/${sport}/${league}/scoreboard?dates=${dateStr}&limit=500`;
  if (groups) url += `&groups=${groups}`;

  try {
    const res = await fetch(url);
    if (!res.ok) return [];
    const data = await res.json() as any;
    const scores: ESPNScore[] = [];

    for (const event of data.events || []) {
      const comp = event.competitions?.[0];
      if (!comp) continue;
      const statusName = comp.status?.type?.name || '';
      if (statusName !== 'STATUS_FINAL' && statusName !== 'STATUS_FULL_TIME') continue;

      const teams = comp.competitors || [];
      const home = teams.find((t: any) => t.homeAway === 'home') || teams[0];
      const away = teams.find((t: any) => t.homeAway === 'away') || teams[1];
      if (!home || !away) continue;

      const homeTeam = getEspnCompetitorName(home);
      const awayTeam = getEspnCompetitorName(away);
      const homeScore = getEspnCompetitorScore(home);
      const awayScore = getEspnCompetitorScore(away);
      if (!homeTeam || !awayTeam || homeScore == null || awayScore == null) continue;

      scores.push({ homeTeam, awayTeam, homeScore, awayScore });
    }

    return scores;
  } catch (err) {
    console.error(`ESPN fetch error for ${sport}/${league} on ${dateStr}:`, err);
    return [];
  }
}

function teamMatchScore(a: string, b: string): number {
  const normalize = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, '');
  const na = normalize(a);
  const nb = normalize(b);
  if (na === nb) return 1.0;

  const lastA = a.trim().split(/\s+/).pop()?.toLowerCase() || '';
  const lastB = b.trim().split(/\s+/).pop()?.toLowerCase() || '';
  if (lastA === lastB && lastA.length > 2) return 0.9;

  if (na.includes(nb) || nb.includes(na)) return 0.8;

  const wordsA = a.trim().split(/\s+/).slice(0, 2).join(' ').toLowerCase();
  const wordsB = b.trim().split(/\s+/).slice(0, 2).join(' ').toLowerCase();
  if (wordsA.length >= 6 && wordsA === wordsB) return 0.75;

  return 0;
}

function toDateKey(input: Date | string | null | undefined): string | null {
  if (!input) return null;
  const date = input instanceof Date ? input : new Date(input);
  if (Number.isNaN(date.getTime())) return null;
  return date.toISOString().slice(0, 10).replace(/-/g, '');
}

function dateVariants(dateKey: string | null): string[] {
  if (!dateKey) return [];
  const iso = `${dateKey.slice(0, 4)}-${dateKey.slice(4, 6)}-${dateKey.slice(6, 8)}T12:00:00Z`;
  const base = new Date(iso);
  if (Number.isNaN(base.getTime())) return [dateKey];

  return [-1, 0, 1].map((offset) => {
    const d = new Date(base.getTime() + offset * 86400000);
    return d.toISOString().slice(0, 10).replace(/-/g, '');
  });
}

async function main() {
  console.log(`[${new Date().toISOString()}] Repairing legacy benchmark integrity...`);

  const { rows } = await pool.query(`
    SELECT
      fa.id,
      fa.event_id,
      fa.league,
      fa.event_date,
      fa.predicted_winner,
      fa.actual_winner,
      fa.home_score,
      fa.away_score,
      fa.final_grade,
      fa.grading_policy,
      fa.forecast_type,
      fa.original_forecast,
      fa.benchmark_forecast,
      fa.closing_market,
      fc.home_team,
      fc.away_team,
      fc.forecast_data,
      fc.odds_data
    FROM rm_forecast_accuracy_v2 fa
    LEFT JOIN rm_forecast_cache fc ON fa.event_id = fc.event_id
    WHERE fa.benchmark_source = 'legacy'
      AND fa.actual_winner IS NOT NULL
      AND fa.final_grade IS NOT NULL
      AND COALESCE(fa.is_preseason, false) = false
      AND fa.forecast_type = 'spread'
    ORDER BY fa.event_date DESC, fa.event_id ASC
  `);

  console.log(`Found ${rows.length} legacy spread rows to audit`);

  const keys = new Set<string>();
  for (const row of rows) {
    if (!ESPN_LEAGUES[row.league]) continue;
    for (const dateKey of dateVariants(toDateKey(row.event_date))) {
      keys.add(`${row.league}:${dateKey}`);
    }
  }

  const scoreCache = new Map<string, ESPNScore[]>();
  for (const key of keys) {
    const [league, dateKey] = key.split(':');
    const espn = ESPN_LEAGUES[league];
    if (!espn) continue;
    const scores = await fetchESPNScores(espn.sport, espn.league, dateKey, espn.groups);
    scoreCache.set(key, scores);
    await new Promise((resolve) => setTimeout(resolve, 150));
  }

  let matched = 0;
  let updated = 0;
  let scoreUpdates = 0;
  let gradeUpdates = 0;
  let reclassified = 0;
  const misses: string[] = [];

  for (const row of rows) {
    if (!ESPN_LEAGUES[row.league] || !row.home_team || !row.away_team) {
      misses.push(row.event_id);
      continue;
    }

    let finalMatch: ESPNScore | null = null;
    let reversed = false;

    for (const dateKey of dateVariants(toDateKey(row.event_date))) {
      const scores = scoreCache.get(`${row.league}:${dateKey}`) || [];
      const direct = scores.find((score) =>
        teamMatchScore(score.homeTeam, row.home_team) >= 0.8 &&
        teamMatchScore(score.awayTeam, row.away_team) >= 0.8
      );
      if (direct) {
        finalMatch = direct;
        break;
      }

      const swapped = scores.find((score) =>
        teamMatchScore(score.awayTeam, row.home_team) >= 0.8 &&
        teamMatchScore(score.homeTeam, row.away_team) >= 0.8
      );
      if (swapped) {
        finalMatch = swapped;
        reversed = true;
        break;
      }
    }

    if (!finalMatch) {
      misses.push(row.event_id);
      continue;
    }

    matched += 1;

    const homeScore = reversed ? finalMatch.awayScore : finalMatch.homeScore;
    const awayScore = reversed ? finalMatch.homeScore : finalMatch.awayScore;
    const actualWinner = homeScore === awayScore
      ? 'DRAW'
      : (homeScore > awayScore ? row.home_team : row.away_team);
    const gradeResult = gradeFromForecastData(
      row.forecast_data,
      row.odds_data,
      row.home_team,
      row.away_team,
      homeScore,
      awayScore,
      row.league,
    );

    const scoreChanged = row.home_score !== homeScore || row.away_score !== awayScore || row.actual_winner !== actualWinner;
    const gradeChanged = !!gradeResult && (
      row.final_grade !== gradeResult.grade ||
      row.benchmark_forecast !== gradeResult.benchmarkForecast ||
      row.original_forecast !== gradeResult.originalForecast ||
      row.closing_market !== gradeResult.closingMarket ||
      row.grading_policy !== gradeResult.gradingPolicy
    );

    if (!scoreChanged && !gradeChanged && (!gradeResult || row.forecast_type === gradeResult.forecastType)) {
      continue;
    }

    await pool.query(
      `UPDATE rm_forecast_accuracy_v2
       SET home_score = $2::integer,
           away_score = $3::integer,
           actual_spread = $2::numeric - $3::numeric,
           actual_total = $2::numeric + $3::numeric,
           actual_winner = $4,
           final_grade = COALESCE($5, final_grade),
           grading_policy = COALESCE($6, grading_policy),
           forecast_type = COALESCE($7, forecast_type),
           original_forecast = COALESCE($8, original_forecast),
           benchmark_forecast = COALESCE($9, benchmark_forecast),
           closing_market = COALESCE($10, closing_market),
           benchmark_source = COALESCE($11, benchmark_source)
       WHERE id = $1`,
      [
        row.id,
        homeScore,
        awayScore,
        actualWinner,
        gradeResult?.grade ?? null,
        gradeResult?.gradingPolicy ?? null,
        gradeResult?.forecastType ?? null,
        gradeResult?.originalForecast ?? null,
        gradeResult?.benchmarkForecast ?? null,
        gradeResult?.closingMarket ?? null,
        gradeResult?.benchmarkSource ?? null,
      ],
    );

    updated += 1;
    if (scoreChanged) scoreUpdates += 1;
    if (gradeChanged) gradeUpdates += 1;
    if (gradeResult) reclassified += 1;
  }

  console.log(JSON.stringify({
    audited: rows.length,
    matched,
    updated,
    scoreUpdates,
    gradeUpdates,
    reclassified,
    misses: misses.length,
  }));

  if (misses.length > 0) {
    console.log(`Unmatched legacy rows: ${misses.slice(0, 20).join(', ')}`);
  }

  await pool.end();
}

main().catch((err) => {
  console.error('Legacy benchmark integrity repair failed:', err);
  process.exit(1);
});
