#!/usr/bin/env npx tsx
/**
 * Backfill Historical Odds from SportsGameOdds API for ALL Leagues
 *
 * Usage:
 *   npx tsx scripts/backfill_all_leagues_sgo.ts
 *   npx tsx scripts/backfill_all_leagues_sgo.ts --league NBA
 *   npx tsx scripts/backfill_all_leagues_sgo.ts --league NHL --from 2024-01-01 --to 2024-12-31
 */

import SportsGameOdds from 'sports-odds-api';
import { PrismaClient } from '../prisma_sports/generated/sports-client';
import * as fs from 'fs';
import * as path from 'path';
import * as crypto from 'crypto';

// Load env
const envPath = path.join('/var/www/html/eventheodds', '.env');
if (fs.existsSync(envPath)) {
  const envContent = fs.readFileSync(envPath, 'utf-8');
  envContent.split('\n').forEach(line => {
    const [key, ...valueParts] = line.split('=');
    if (key && valueParts.length) {
      const value = valueParts.join('=').replace(/^["']|["']$/g, '');
      process.env[key.trim()] = value.trim();
    }
  });
}

const API_KEY = process.env.SPORTSGAMEODDS_API_KEY;
if (!API_KEY) {
  console.error('ERROR: SPORTSGAMEODDS_API_KEY not set');
  process.exit(1);
}

const prisma = new PrismaClient({
  datasources: {
    db: { url: process.env.SPORTS_DATABASE_URL }
  }
});

const client = new SportsGameOdds({
  apiKeyHeader: API_KEY,
});

// League configurations with their typical seasons
const LEAGUE_CONFIGS: Record<string, {
  seasonStart: string;
  seasonEnd: string;
  years: number[];
}> = {
  'NHL': {
    seasonStart: '10-01',
    seasonEnd: '06-30',
    years: [2024, 2025]
  },
  'NBA': {
    seasonStart: '10-15',
    seasonEnd: '06-30',
    years: [2024, 2025]
  },
  'NFL': {
    seasonStart: '09-01',
    seasonEnd: '02-15',
    years: [2024, 2025]
  },
  'MLB': {
    seasonStart: '03-20',
    seasonEnd: '11-05',
    years: [2024, 2025]
  },
  'NCAAB': {
    seasonStart: '11-01',
    seasonEnd: '04-10',
    years: [2024, 2025, 2026]
  },
  'NCAAF': {
    seasonStart: '08-20',
    seasonEnd: '01-15',
    years: [2024, 2025]
  },
  'WNBA': {
    seasonStart: '05-01',
    seasonEnd: '10-31',
    years: [2024, 2025]
  },
};

function generateGameId(league: string, gameDate: string, homeTeam: string, awayTeam: string): number {
  const key = `${league}:${gameDate}:${homeTeam}:${awayTeam}`;
  const hash = crypto.createHash('md5').update(key).digest();
  return hash.readUInt32BE(0) % (2 ** 31);
}

function parseAmericanOdds(oddsStr: string | number | undefined): number | null {
  if (oddsStr === undefined || oddsStr === null) return null;
  const num = typeof oddsStr === 'string' ? parseInt(oddsStr.replace(/[^0-9-+]/g, '')) : oddsStr;
  if (isNaN(num)) return null;
  return num;
}

async function backfillLeague(league: string, startDate: string, endDate: string) {
  console.log(`\n${'='.repeat(60)}`);
  console.log(`BACKFILLING ${league} ODDS FROM SPORTSGAMEODDS`);
  console.log(`Date range: ${startDate} to ${endDate}`);
  console.log('='.repeat(60));
  console.log();

  let totalEvents = 0;
  let totalRecords = 0;
  let eventsWithOdds = 0;

  try {
    let page = await client.events.get({
      leagueID: league,
      startsAfter: startDate,
      startsBefore: endDate,
      ended: true,
      limit: 100,
    });

    while (true) {
      const events = page.data || [];
      if (events.length === 0) break;

      console.log(`Processing batch of ${events.length} events...`);

      for (const event of events) {
        if (!event.teams?.home || !event.teams?.away) continue;
        if (!event.status?.startsAt) continue;

        const gameDate = new Date(event.status.startsAt);
        const gameDateStr = gameDate.toISOString().split('T')[0];

        const homeTeam = event.teams.home.names?.short || event.teams.home.names?.medium || 'UNK';
        const awayTeam = event.teams.away.names?.short || event.teams.away.names?.medium || 'UNK';

        if (!homeTeam || !awayTeam) continue;

        const gameId = generateGameId(league.toLowerCase(), gameDateStr, homeTeam, awayTeam);
        totalEvents++;

        const records: any[] = [];
        const odds = event.odds || {};

        // Extract moneyline - look for game-level ML markets
        const mlHome = odds['points-home-game-ml-home'] as any;
        const mlAway = odds['points-away-game-ml-away'] as any;

        if (mlHome || mlAway) {
          const homeOdds = parseAmericanOdds(mlHome?.bookOdds);
          const awayOdds = parseAmericanOdds(mlAway?.bookOdds);

          if (homeOdds !== null || awayOdds !== null) {
            records.push({
              gameId: BigInt(gameId),
              league: league.toLowerCase(),
              gameDate,
              homeTeam,
              awayTeam,
              bookmaker: 'consensus',
              market: 'moneyline',
              lineValue: null,
              homeOdds,
              awayOdds,
              source: 'sportsgameodds',
              fetchedAt: new Date(),
            });

            // Also extract individual bookmaker odds
            const allBookmakers = { ...(mlHome?.byBookmaker || {}), ...(mlAway?.byBookmaker || {}) };
            for (const [bookmaker, bookData] of Object.entries(allBookmakers)) {
              if (bookmaker === 'unknown') continue;
              const bData = bookData as any;
              const bHomeOdds = parseAmericanOdds(mlHome?.byBookmaker?.[bookmaker]?.odds);
              const bAwayOdds = parseAmericanOdds(mlAway?.byBookmaker?.[bookmaker]?.odds);

              if (bHomeOdds !== null || bAwayOdds !== null) {
                records.push({
                  gameId: BigInt(gameId),
                  league: league.toLowerCase(),
                  gameDate,
                  homeTeam,
                  awayTeam,
                  bookmaker: bookmaker.toLowerCase(),
                  market: 'moneyline',
                  lineValue: null,
                  homeOdds: bHomeOdds,
                  awayOdds: bAwayOdds,
                  source: 'sportsgameodds',
                  fetchedAt: new Date(),
                });
              }
            }
          }
        }

        // Extract spread - look for game-level spread markets
        const spHome = odds['points-home-game-sp-home'] as any;
        const spAway = odds['points-away-game-sp-away'] as any;

        if (spHome || spAway) {
          const lineValue = parseFloat(spHome?.bookOverUnder || spAway?.bookOverUnder || '0') || null;
          const homeOdds = parseAmericanOdds(spHome?.bookOdds);
          const awayOdds = parseAmericanOdds(spAway?.bookOdds);

          if ((homeOdds !== null || awayOdds !== null) && lineValue !== null) {
            records.push({
              gameId: BigInt(gameId),
              league: league.toLowerCase(),
              gameDate,
              homeTeam,
              awayTeam,
              bookmaker: 'consensus',
              market: 'spread',
              lineValue,
              homeOdds,
              awayOdds,
              source: 'sportsgameodds',
              fetchedAt: new Date(),
            });
          }
        }

        // Also try reg (regulation) spreads for hockey
        const spHomeReg = odds['points-home-reg-sp-home'] as any;
        const spAwayReg = odds['points-away-reg-sp-away'] as any;

        if (spHomeReg || spAwayReg) {
          const lineValue = parseFloat(spHomeReg?.bookOverUnder || spAwayReg?.bookOverUnder || '1.5');
          const homeOdds = parseAmericanOdds(spHomeReg?.bookOdds);
          const awayOdds = parseAmericanOdds(spAwayReg?.bookOdds);

          if (homeOdds !== null || awayOdds !== null) {
            records.push({
              gameId: BigInt(gameId),
              league: league.toLowerCase(),
              gameDate,
              homeTeam,
              awayTeam,
              bookmaker: 'consensus',
              market: 'spread',
              lineValue: lineValue || 1.5,
              homeOdds,
              awayOdds,
              source: 'sportsgameodds',
              fetchedAt: new Date(),
            });
          }
        }

        // Extract totals - look for game-level over/under
        const ouOver = odds['points-all-game-ou-over'] as any;
        const ouUnder = odds['points-all-game-ou-under'] as any;

        if (ouOver || ouUnder) {
          const lineValue = parseFloat(ouOver?.bookOverUnder || ouUnder?.bookOverUnder || '0') || null;
          const overOdds = parseAmericanOdds(ouOver?.bookOdds);
          const underOdds = parseAmericanOdds(ouUnder?.bookOdds);

          if ((overOdds !== null || underOdds !== null) && lineValue !== null) {
            records.push({
              gameId: BigInt(gameId),
              league: league.toLowerCase(),
              gameDate,
              homeTeam,
              awayTeam,
              bookmaker: 'consensus',
              market: 'total',
              lineValue,
              homeOdds: overOdds,  // over
              awayOdds: underOdds, // under
              source: 'sportsgameodds',
              fetchedAt: new Date(),
            });

            // Extract bookmaker-specific totals
            const allBookmakers = { ...(ouOver?.byBookmaker || {}), ...(ouUnder?.byBookmaker || {}) };
            for (const bookmaker of Object.keys(allBookmakers)) {
              if (bookmaker === 'unknown') continue;
              const bOverOdds = parseAmericanOdds(ouOver?.byBookmaker?.[bookmaker]?.odds);
              const bUnderOdds = parseAmericanOdds(ouUnder?.byBookmaker?.[bookmaker]?.odds);
              const bLine = parseFloat(ouOver?.byBookmaker?.[bookmaker]?.overUnder || ouUnder?.byBookmaker?.[bookmaker]?.overUnder || String(lineValue));

              if (bOverOdds !== null || bUnderOdds !== null) {
                records.push({
                  gameId: BigInt(gameId),
                  league: league.toLowerCase(),
                  gameDate,
                  homeTeam,
                  awayTeam,
                  bookmaker: bookmaker.toLowerCase(),
                  market: 'total',
                  lineValue: bLine,
                  homeOdds: bOverOdds,
                  awayOdds: bUnderOdds,
                  source: 'sportsgameodds',
                  fetchedAt: new Date(),
                });
              }
            }
          }
        }

        if (records.length === 0) continue;
        eventsWithOdds++;

        // Dedupe records
        const uniqueRecords = records.filter((r, i, arr) =>
          i === arr.findIndex(x =>
            x.gameId === r.gameId &&
            x.bookmaker === r.bookmaker &&
            x.market === r.market &&
            x.lineValue === r.lineValue
          )
        );

        // Upsert records
        for (const record of uniqueRecords) {
          try {
            await prisma.bookmakerOdds.upsert({
              where: {
                gameId_bookmaker_market_lineValue: {
                  gameId: record.gameId,
                  bookmaker: record.bookmaker,
                  market: record.market,
                  lineValue: record.lineValue,
                }
              },
              update: {
                homeOdds: record.homeOdds,
                awayOdds: record.awayOdds,
                fetchedAt: record.fetchedAt,
              },
              create: record,
            });
            totalRecords++;
          } catch (e: any) {
            if (!e.message?.includes('Unique constraint')) {
              // console.error(`Error inserting record: ${e.message}`);
            }
          }
        }
      }

      // Check for more pages
      if (!page.hasNextPage) break;

      try {
        page = await page.getNextPage();
      } catch (e) {
        console.log('No more pages available');
        break;
      }
    }

    console.log(`\n${league} COMPLETE:`);
    console.log(`  Events processed: ${totalEvents}`);
    console.log(`  Events with odds: ${eventsWithOdds}`);
    console.log(`  Records inserted/updated: ${totalRecords}`);

    return { league, totalEvents, eventsWithOdds, totalRecords };

  } catch (e: any) {
    console.error(`Error backfilling ${league}: ${e.message}`);
    return { league, totalEvents, eventsWithOdds, totalRecords, error: e.message };
  }
}

async function main() {
  const args = process.argv.slice(2);
  let targetLeague: string | null = null;
  let fromDate: string | null = null;
  let toDate: string | null = null;

  for (let i = 0; i < args.length; i++) {
    if (args[i] === '--league' && args[i + 1]) {
      targetLeague = args[i + 1].toUpperCase();
    } else if (args[i] === '--from' && args[i + 1]) {
      fromDate = args[i + 1];
    } else if (args[i] === '--to' && args[i + 1]) {
      toDate = args[i + 1];
    }
  }

  const leagues = targetLeague ? [targetLeague] : Object.keys(LEAGUE_CONFIGS);
  const results: any[] = [];

  console.log('='.repeat(60));
  console.log('SPORTSGAMEODDS MULTI-LEAGUE BACKFILL');
  console.log('='.repeat(60));
  console.log(`Leagues: ${leagues.join(', ')}`);
  console.log();

  for (const league of leagues) {
    const config = LEAGUE_CONFIGS[league];
    if (!config) {
      console.log(`Unknown league: ${league}, skipping...`);
      continue;
    }

    // Run for each configured year
    for (const year of config.years) {
      const startMonth = parseInt(config.seasonStart.split('-')[0]);
      const endMonth = parseInt(config.seasonEnd.split('-')[0]);

      let start: string;
      let end: string;

      if (startMonth > endMonth) {
        // Season crosses year boundary (e.g., NHL Oct-Jun)
        start = fromDate || `${year - 1}-${config.seasonStart}`;
        end = toDate || `${year}-${config.seasonEnd}`;
      } else {
        // Season within single year (e.g., MLB Mar-Nov)
        start = fromDate || `${year}-${config.seasonStart}`;
        end = toDate || `${year}-${config.seasonEnd}`;
      }

      const result = await backfillLeague(league, start, end);
      results.push(result);

      // Rate limiting
      await new Promise(r => setTimeout(r, 500));
    }
  }

  // Print summary
  console.log('\n' + '='.repeat(60));
  console.log('BACKFILL SUMMARY');
  console.log('='.repeat(60));
  console.log();

  let grandTotalEvents = 0;
  let grandTotalRecords = 0;

  for (const r of results) {
    console.log(`${r.league}: ${r.totalEvents} events, ${r.eventsWithOdds} with odds, ${r.totalRecords} records${r.error ? ' (ERROR: ' + r.error + ')' : ''}`);
    grandTotalEvents += r.totalEvents || 0;
    grandTotalRecords += r.totalRecords || 0;
  }

  console.log();
  console.log(`TOTAL: ${grandTotalEvents} events, ${grandTotalRecords} records`);

  await prisma.$disconnect();
}

main().catch(console.error);
