/**
 * FanGraphs MLB Data Sync Worker
 *
 * Pulls batting leaders, pitching leaders, and Steamer projections from
 * FanGraphs public API. Stores in fg_batting_stats, fg_pitching_stats,
 * fg_projections tables.
 *
 * Usage: npx tsx src/workers/fangraphs-sync.ts [--dry-run]
 * Cron:  3:30 AM ET daily (before 6 AM forecast generation)
 */

import 'dotenv/config';
import pool from '../db';
import {
  fetchBattingLeaders,
  fetchPitchingLeaders,
  fetchBattingProjections,
  fetchPitchingProjections,
  upsertBatters,
  upsertPitchers,
  upsertProjections,
} from '../services/fangraphs';

const DRY_RUN = process.argv.includes('--dry-run');

function sleep(ms: number) {
  return new Promise(r => setTimeout(r, ms));
}

function getCurrentSeason(): number {
  const now = new Date();
  const m = now.getMonth(); // 0-indexed
  // MLB regular season starts late March. Before that, 2025 data is
  // the most recent full-season data. FanGraphs has 2026 projections
  // but no 2026 leaderboard stats until games are played.
  // Use current year for projections, previous year for stats if before April.
  return now.getFullYear();
}

function getStatsSeason(): number {
  const now = new Date();
  // Before late March, use previous season for batting/pitching leaders
  if (now.getMonth() < 3 || (now.getMonth() === 2 && now.getDate() < 25)) {
    return now.getFullYear() - 1;
  }
  return now.getFullYear();
}

async function main() {
  const startTime = Date.now();
  const season = getCurrentSeason();

  console.log('='.repeat(60));
  console.log('FANGRAPHS MLB DATA SYNC');
  console.log(`Season: ${season} | Mode: ${DRY_RUN ? 'DRY RUN' : 'LIVE'}`);
  console.log(`Date: ${new Date().toISOString()}`);
  console.log('='.repeat(60));

  const statsSeason = getStatsSeason();
  console.log(`Stats season: ${statsSeason} (projections: ${season})`);

  const results: { label: string; count: number; error?: string }[] = [];

  // 1. Batting leaders — full season
  try {
    console.log('\n[1/8] Fetching batting leaders (full season)...');
    const batFull = await fetchBattingLeaders(statsSeason, 'full');
    console.log(`  → ${batFull.length} batters`);
    if (!DRY_RUN && batFull.length > 0) {
      const n = await upsertBatters(batFull, statsSeason, 'full');
      results.push({ label: 'Batting (full)', count: n });
    } else {
      results.push({ label: 'Batting (full)', count: batFull.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Batting (full)', count: 0, error: err.message });
  }
  await sleep(500);

  // 2. Batting leaders — last 14 days
  try {
    console.log('[2/8] Fetching batting leaders (last 14 days)...');
    const batRecent = await fetchBattingLeaders(statsSeason, 'last14');
    console.log(`  → ${batRecent.length} batters`);
    if (!DRY_RUN && batRecent.length > 0) {
      const n = await upsertBatters(batRecent, statsSeason, 'last14');
      results.push({ label: 'Batting (last14)', count: n });
    } else {
      results.push({ label: 'Batting (last14)', count: batRecent.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Batting (last14)', count: 0, error: err.message });
  }
  await sleep(500);

  // 3. Batting leaders — last 30 days
  try {
    console.log('[3/8] Fetching batting leaders (last 30 days)...');
    const bat30 = await fetchBattingLeaders(statsSeason, 'last30');
    console.log(`  → ${bat30.length} batters`);
    if (!DRY_RUN && bat30.length > 0) {
      const n = await upsertBatters(bat30, statsSeason, 'last30');
      results.push({ label: 'Batting (last30)', count: n });
    } else {
      results.push({ label: 'Batting (last30)', count: bat30.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Batting (last30)', count: 0, error: err.message });
  }
  await sleep(500);

  // 4. Pitching leaders — full season
  try {
    console.log('[4/8] Fetching pitching leaders (full season)...');
    const pitFull = await fetchPitchingLeaders(statsSeason, 'full');
    console.log(`  → ${pitFull.length} pitchers`);
    if (!DRY_RUN && pitFull.length > 0) {
      const n = await upsertPitchers(pitFull, statsSeason, 'full');
      results.push({ label: 'Pitching (full)', count: n });
    } else {
      results.push({ label: 'Pitching (full)', count: pitFull.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Pitching (full)', count: 0, error: err.message });
  }
  await sleep(500);

  // 5. Pitching leaders — last 14 days
  try {
    console.log('[5/8] Fetching pitching leaders (last 14 days)...');
    const pitRecent = await fetchPitchingLeaders(statsSeason, 'last14');
    console.log(`  → ${pitRecent.length} pitchers`);
    if (!DRY_RUN && pitRecent.length > 0) {
      const n = await upsertPitchers(pitRecent, statsSeason, 'last14');
      results.push({ label: 'Pitching (last14)', count: n });
    } else {
      results.push({ label: 'Pitching (last14)', count: pitRecent.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Pitching (last14)', count: 0, error: err.message });
  }
  await sleep(500);

  // 6. Pitching leaders — last 30 days
  try {
    console.log('[6/8] Fetching pitching leaders (last 30 days)...');
    const pit30 = await fetchPitchingLeaders(statsSeason, 'last30');
    console.log(`  → ${pit30.length} pitchers`);
    if (!DRY_RUN && pit30.length > 0) {
      const n = await upsertPitchers(pit30, statsSeason, 'last30');
      results.push({ label: 'Pitching (last30)', count: n });
    } else {
      results.push({ label: 'Pitching (last30)', count: pit30.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Pitching (last30)', count: 0, error: err.message });
  }
  await sleep(500);

  // 7. Steamer batting projections
  try {
    console.log('[7/8] Fetching Steamer batting projections...');
    const batProj = await fetchBattingProjections(season, 'steamer');
    console.log(`  → ${batProj.length} batters projected`);
    if (!DRY_RUN && batProj.length > 0) {
      const n = await upsertProjections(batProj, season);
      results.push({ label: 'Projections (bat)', count: n });
    } else {
      results.push({ label: 'Projections (bat)', count: batProj.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Projections (bat)', count: 0, error: err.message });
  }
  await sleep(500);

  // 8. Steamer pitching projections
  try {
    console.log('[8/8] Fetching Steamer pitching projections...');
    const pitProj = await fetchPitchingProjections(season, 'steamer');
    console.log(`  → ${pitProj.length} pitchers projected`);
    if (!DRY_RUN && pitProj.length > 0) {
      const n = await upsertProjections(pitProj, season);
      results.push({ label: 'Projections (pit)', count: n });
    } else {
      results.push({ label: 'Projections (pit)', count: pitProj.length });
    }
  } catch (err: any) {
    console.error(`  ERROR: ${err.message}`);
    results.push({ label: 'Projections (pit)', count: 0, error: err.message });
  }

  // Cleanup: delete data older than 30 days
  if (!DRY_RUN) {
    try {
      const { rowCount: r1 } = await pool.query(`DELETE FROM fg_batting_stats WHERE pull_date < CURRENT_DATE - 30`);
      const { rowCount: r2 } = await pool.query(`DELETE FROM fg_pitching_stats WHERE pull_date < CURRENT_DATE - 30`);
      const { rowCount: r3 } = await pool.query(`DELETE FROM fg_projections WHERE pull_date < CURRENT_DATE - 30`);
      const cleaned = (r1 || 0) + (r2 || 0) + (r3 || 0);
      if (cleaned > 0) console.log(`\nCleaned ${cleaned} rows older than 30 days`);
    } catch {}
  }

  // Summary
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
  const totalRows = results.reduce((s, r) => s + r.count, 0);
  const errors = results.filter(r => r.error);

  console.log('\n' + '='.repeat(60));
  console.log('FANGRAPHS SYNC COMPLETE');
  console.log('='.repeat(60));
  for (const r of results) {
    console.log(`  ${r.label.padEnd(25)} ${String(r.count).padStart(5)} ${r.error ? '⚠️ ' + r.error.slice(0, 50) : '✓'}`);
  }
  console.log(`\nTotal: ${totalRows} rows | Errors: ${errors.length} | Time: ${elapsed}s`);

  await pool.end();
  process.exit(errors.length > 0 ? 1 : 0);
}

// Hard timeout: kill the process if it runs longer than 10 minutes
const HARD_TIMEOUT_MS = 10 * 60 * 1000;
const hardTimer = setTimeout(() => {
  console.error(`FATAL: Worker exceeded ${HARD_TIMEOUT_MS / 1000}s hard timeout — killing process`);
  process.exit(2);
}, HARD_TIMEOUT_MS);
hardTimer.unref();

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