/**
 * Seed All Sports — Auto-seeds today's events from SGO API across all leagues.
 *
 * For each league in LEAGUE_MAP:
 * 1. Fetches events from SGO with startsAfter = start of today ET
 * 2. Filters to only today's games (ET timezone)
 * 3. Parses odds via parseOdds()
 * 4. Counts PIFF 3.0 props per event
 * 5. Upserts into rm_events with ON CONFLICT for odds refresh
 *
 * Usage: npx tsx src/workers/seed-all-sports.ts
 * Cron: 30 min before daily-forecasts (5:30 AM ET and 11:30 AM ET)
 */

import 'dotenv/config';
import pool from '../db';
import { ACTIVE_SEEDED_LEAGUES } from '../config/league-support';
import { deriveEventLifecycleStatus, fetchEvents, parseOdds, sanitizeGameOddsForLeague, LEAGUE_MAP, SgoEvent, isNcaabD1 } from '../services/sgo';
import { loadPiffPropsForDate, PiffLeg } from '../services/piff';
import { canonicalizeTeamName } from '../lib/team-abbreviations';
import { EU_EVENT_LOOKAHEAD_DAYS, getEtDateKey, getLeagueLookaheadDays, isLeagueEventInWindow } from '../lib/league-windows';
import { fetchCurrentOdds, getTheOddsSportKey, hasTheOddsApiConfigured, matchCurrentGameOdds, mergeMissingGameOdds, type TheOddsCurrentEvent } from '../services/the-odds';
import { reconcileStartTimeFromCurrentOdds } from '../services/start-time-reconciliation';

function getTodayET(): { dateStr: string; startOfDay: string; endOfDay: string } {
  const now = new Date();
  const etStr = now.toLocaleString('en-US', { timeZone: 'America/New_York' });
  const et = new Date(etStr);
  const y = et.getFullYear();
  const m = String(et.getMonth() + 1).padStart(2, '0');
  const d = String(et.getDate()).padStart(2, '0');
  const dateStr = `${y}-${m}-${d}`;
  // Start of day in ET (approx UTC offset)
  const startOfDay = `${dateStr}T00:00:00-05:00`;
  const endOfDay = `${dateStr}T23:59:59-05:00`;
  return { dateStr, startOfDay, endOfDay };
}

function makeEventId(league: string, awayShort: string, homeShort: string, dateStr: string): string {
  const datePart = dateStr.replace(/-/g, '');
  return `${league}-${awayShort.toLowerCase()}-${homeShort.toLowerCase()}-${datePart}`;
}

function countPiffProps(league: string, homeShort: string, awayShort: string, piffMap: Record<string, PiffLeg[]>): number {
  const homeProps = piffMap[`${league}:${homeShort.toUpperCase()}`] || [];
  const awayProps = piffMap[`${league}:${awayShort.toUpperCase()}`] || [];
  return homeProps.length + awayProps.length;
}

async function main() {
  const startTime = Date.now();
  const { dateStr, startOfDay } = getTodayET();

  console.log('='.repeat(60));
  console.log('RAINMAKER ALL-SPORT SEEDER');
  console.log(`Date: ${dateStr} | Run: ${new Date().toISOString()}`);
  console.log('='.repeat(60));

  const piffMapsByDate = new Map<string, Record<string, PiffLeg[]>>();

  let totalSeeded = 0;
  let totalUpdated = 0;
  let totalSkipped = 0;

  for (const league of ACTIVE_SEEDED_LEAGUES) {
    if (!LEAGUE_MAP[league]) {
      console.log(`\n[SKIP] ${league.toUpperCase()} — not in LEAGUE_MAP`);
      continue;
    }

    try {
      // Fetch with startsAfter to get only upcoming events
      const events = await fetchEvents(league, startOfDay);

      if (events.length === 0) {
        console.log(`\n[${league.toUpperCase()}] No events from API (offseason or no games)`);
        continue;
      }

      const lookaheadDays = getLeagueLookaheadDays(league, EU_EVENT_LOOKAHEAD_DAYS, 0);
      const targetEvents = events.filter(e => {
        const startsAt = e.status?.startsAt;
        if (!startsAt) return false;
        if (!isLeagueEventInWindow(league, startsAt, lookaheadDays)) return false;
        // MLB preseason date gate removed — regular season MLB should seed normally.
        return true;
      });

      if (targetEvents.length === 0) {
        const firstFeedDate = events
          .map((event) => getEtDateKey(event.status?.startsAt || ''))
          .find(Boolean);
        const windowLabel = lookaheadDays > 0 ? `today + ${lookaheadDays}d` : 'today';
        console.log(`\n[${league.toUpperCase()}] ${events.length} events from API, 0 in ${windowLabel}${firstFeedDate ? ` (first feed date ${firstFeedDate})` : ''}`);
        continue;
      }

      const windowLabel = lookaheadDays > 0 ? `next ${lookaheadDays + 1} ET days` : 'today';
      console.log(`\n[${league.toUpperCase()}] ${events.length} from API → ${targetEvents.length} in ${windowLabel}`);

      let leagueSeeded = 0;
      let leagueUpdated = 0;
      let currentOddsEvents: TheOddsCurrentEvent[] = [];

      if (hasTheOddsApiConfigured()) {
        const sportKey = getTheOddsSportKey(league);
        if (sportKey) {
          try {
            currentOddsEvents = await fetchCurrentOdds({
              sportKey,
              markets: ['h2h', 'spreads', 'totals'],
            });
          } catch (err: any) {
            console.warn(`  [WARN] ${league.toUpperCase()} start-time reconciliation skipped: ${err.message}`);
          }
        }
      }

      for (const event of targetEvents) {
        if (!event.teams?.home?.names || !event.teams?.away?.names) {
          totalSkipped++;
          continue;
        }

        const homeTeam = canonicalizeTeamName(event.teams.home.names.long);
        const homeShort = event.teams.home.names.short;
        const awayTeam = canonicalizeTeamName(event.teams.away.names.long);
        const awayShort = event.teams.away.names.short;

        // Skip non-D1 NCAAB teams (D2/D3/NAIA have no grading source)
        if (league === 'ncaab') {
          const [homeD1, awayD1] = await Promise.all([isNcaabD1(homeTeam), isNcaabD1(awayTeam)]);
          if (!homeD1 || !awayD1) {
            console.log(`  [SKIP] Non-D1: ${awayShort} @ ${homeShort}`);
            totalSkipped++;
            continue;
          }
        }
        const startsAt = event.status.startsAt;
        const reconciledStart = reconcileStartTimeFromCurrentOdds({
          homeTeam,
          awayTeam,
          startsAt,
          currentEvents: currentOddsEvents,
        });
        const eventStatus = deriveEventLifecycleStatus(event);
        const eventDateStr = getEtDateKey(reconciledStart.startsAt);
        if (!eventDateStr) {
          totalSkipped++;
          continue;
        }

        const eventId = makeEventId(league, awayShort, homeShort, eventDateStr);
        const sgoOdds = parseOdds(event);
        const fallbackOdds = matchCurrentGameOdds(currentOddsEvents, {
          homeTeam,
          awayTeam,
          startsAt: reconciledStart.startsAt,
        });
        const mergedOdds = mergeMissingGameOdds({
          moneyline: sgoOdds.moneyline,
          spread: sgoOdds.spread,
          total: sgoOdds.total,
        }, fallbackOdds);
        const odds = sanitizeGameOddsForLeague(league, mergedOdds);
        if (!piffMapsByDate.has(eventDateStr)) {
          piffMapsByDate.set(eventDateStr, loadPiffPropsForDate(eventDateStr));
        }
        const piffCount = countPiffProps(league, homeShort, awayShort, piffMapsByDate.get(eventDateStr)!);
        // Count SGO raw player props from odds data (unique players)
        const sgoPlayers = new Set(sgoOdds.props.map(p => p.player));
        const propCount = Math.max(piffCount, sgoPlayers.size);

        const result = await pool.query(
          `INSERT INTO rm_events (event_id, league, home_team, home_short, away_team, away_short, starts_at, status, moneyline, spread, total, prop_count, source)
           VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
           ON CONFLICT (event_id) DO UPDATE SET
             status = EXCLUDED.status,
             moneyline = EXCLUDED.moneyline,
             spread = EXCLUDED.spread,
             total = EXCLUDED.total,
             prop_count = EXCLUDED.prop_count,
             source = EXCLUDED.source,
             updated_at = NOW()
           RETURNING (xmax = 0) AS inserted`,
          [
            eventId, league,
            homeTeam, homeShort,
            awayTeam, awayShort,
            reconciledStart.startsAt, eventStatus,
            JSON.stringify(odds.moneyline),
            JSON.stringify(odds.spread),
            JSON.stringify(odds.total),
            propCount, fallbackOdds ? 'sgo-auto+theodds' : 'sgo-auto',
          ]
        );

        const wasInsert = result.rows[0]?.inserted;
        if (wasInsert) {
          leagueSeeded++;
        } else {
          leagueUpdated++;
        }

        const piffTag = propCount > 0 ? ` [PIFF: ${propCount} props]` : '';
        const mlStr = odds.moneyline.home !== null
          ? `ML: ${odds.moneyline.away}/${odds.moneyline.home}`
          : 'ML: N/A';
        const oddsTag = fallbackOdds && (sgoOdds.moneyline.home == null && odds.moneyline.home != null) ? ' [THEODDS ML]' : '';
        const startTag = reconciledStart.reconciled ? ' [START RECONCILED]' : '';
        console.log(`  ${wasInsert ? '[NEW]' : '[UPD]'} ${awayShort} @ ${homeShort} — ${mlStr}${piffTag}${oddsTag}${startTag}`);
      }

      totalSeeded += leagueSeeded;
      totalUpdated += leagueUpdated;
      console.log(`  → ${leagueSeeded} new, ${leagueUpdated} updated`);

    } catch (err: any) {
      console.error(`\n[${league.toUpperCase()}] ERROR: ${err.message}`);
    }

    // Small delay between leagues to avoid rate limiting
    await new Promise(r => setTimeout(r, 500));
  }

  const elapsed = Math.round((Date.now() - startTime) / 1000);

  console.log('\n' + '='.repeat(60));
  console.log('SEEDING COMPLETE');
  console.log(`New events:     ${totalSeeded}`);
  console.log(`Odds updated:   ${totalUpdated}`);
  console.log(`Skipped:        ${totalSkipped}`);
  console.log(`Time:           ${elapsed}s`);
  console.log('='.repeat(60));

  await pool.end();
  process.exit(0);
}

main().catch((err) => {
  console.error('Fatal error:', err);
  process.exit(1);
});
