#!/usr/bin/env npx tsx
/**
 * Sync Team Rosters Script
 *
 * Fetches current team rosters from ESPN and updates canonical player team assignments.
 * Usage:
 *   npx tsx scripts/sync-rosters.ts --league=nba
 *   npx tsx scripts/sync-rosters.ts --league=nhl
 *   npx tsx scripts/sync-rosters.ts  # all leagues
 */

import { PrismaClient } from '../prisma_sports/generated/sports-client';

const prisma = new PrismaClient();

const ESPN_BASE = 'https://site.api.espn.com/apis/site/v2/sports';

// ESPN team IDs by league
const LEAGUE_CONFIG: Record<string, { sport: string; espnLeague: string }> = {
  nba: { sport: 'basketball', espnLeague: 'nba' },
  nfl: { sport: 'football', espnLeague: 'nfl' },
  nhl: { sport: 'hockey', espnLeague: 'nhl' },
  mlb: { sport: 'baseball', espnLeague: 'mlb' },
};

// ESPN abbreviations that differ from our canonical abbreviations
const ESPN_ABBR_MAP: Record<string, Record<string, string>> = {
  nba: {
    'GS': 'GSW',
    'NO': 'NOP',
    'NY': 'NYK',
    'SA': 'SAS',
    'UTAH': 'UTA',
    'WSH': 'WAS',
  },
  nfl: {
    'WSH': 'WAS',
    'JAX': 'JAX',  // Our canonical uses JAX
  },
  nhl: {
    'TB': 'TB',      // Our canonical uses TB
    'NJ': 'NJD',     // Our canonical uses NJD
    'SJ': 'SJ',      // Our canonical uses SJ
    'LA': 'LA',      // Our canonical uses LA
    'WSH': 'WAS',    // Our canonical uses WAS
    'UTAH': 'UTA',   // Utah Hockey Club
  },
  mlb: {
    'WSH': 'WAS',
    'CHW': 'CWS',
    'KC': 'KCR',
    'TB': 'TBR',
    'SD': 'SDP',
    'SF': 'SFG',
  },
};

function mapEspnAbbr(league: string, espnAbbr: string): string {
  return ESPN_ABBR_MAP[league]?.[espnAbbr] || espnAbbr;
}

function normalizePlayerName(name: string): string {
  if (!name) return '';
  return name
    .toLowerCase()
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .replace(/\s+(jr\.?|sr\.?|iii|ii|iv|v)$/i, '')
    .replace(/\./g, '')
    .replace(/\s+/g, ' ')
    .trim();
}

// Create a condensed version for matching SGO-style names (no hyphens, apostrophes, spaces in middle of names)
function condenseName(name: string): string {
  return normalizePlayerName(name)
    .replace(/[-']/g, '')  // Remove hyphens and apostrophes
    .replace(/\s+/g, '');   // Remove all spaces
}

async function fetchTeamsForLeague(league: string): Promise<Array<{ id: string; abbr: string; name: string }>> {
  const config = LEAGUE_CONFIG[league];
  if (!config) return [];

  const url = `${ESPN_BASE}/${config.sport}/${config.espnLeague}/teams?limit=50`;

  try {
    const response = await fetch(url);
    if (!response.ok) {
      console.error(`[ESPN] Failed to fetch teams for ${league}: ${response.status}`);
      return [];
    }
    const data = await response.json();
    const teams = data?.sports?.[0]?.leagues?.[0]?.teams || [];

    return teams.map((t: any) => ({
      id: t.team?.id || '',
      abbr: t.team?.abbreviation || '',
      name: t.team?.displayName || '',
    }));
  } catch (e) {
    console.error(`[ESPN] Error fetching teams for ${league}:`, e);
    return [];
  }
}

async function fetchRosterForTeam(
  league: string,
  espnTeamId: string
): Promise<Array<{ name: string; position: string }>> {
  const config = LEAGUE_CONFIG[league];
  if (!config) return [];

  const url = `${ESPN_BASE}/${config.sport}/${config.espnLeague}/teams/${espnTeamId}/roster`;

  try {
    const response = await fetch(url);
    if (!response.ok) {
      return [];
    }
    const data = await response.json();

    // ESPN roster structure varies by sport
    const athletes = data?.athletes || [];
    const roster: Array<{ name: string; position: string }> = [];

    // Handle both formats: flat array of athletes OR grouped by position
    for (const item of athletes) {
      // Check if it's a direct athlete object (has fullName)
      if (item?.fullName || item?.displayName) {
        roster.push({
          name: item?.fullName || item?.displayName || '',
          position: item?.position?.abbreviation || '',
        });
      } else if (item?.items) {
        // Grouped format (e.g., by position)
        for (const player of item.items) {
          roster.push({
            name: player?.fullName || player?.displayName || '',
            position: player?.position?.abbreviation || '',
          });
        }
      }
    }

    return roster;
  } catch (e) {
    return [];
  }
}

async function syncRosters(leagues: string[]) {
  console.log('='.repeat(60));
  console.log('ROSTER SYNC');
  console.log('='.repeat(60));
  console.log(`Leagues: ${leagues.join(', ').toUpperCase()}\n`);

  for (const league of leagues) {
    console.log(`\n=== ${league.toUpperCase()} ===\n`);

    // Get our canonical teams
    const canonicalTeams = await prisma.canonicalTeam.findMany({
      where: { league },
      select: { id: true, abbr: true, fullName: true },
    });
    const teamByAbbr = new Map(canonicalTeams.map(t => [t.abbr, t]));

    // Pre-build condensed name lookup for fuzzy matching
    const playersWithoutTeam = await prisma.canonicalPlayer.findMany({
      where: { league, teamId: null },
      select: { id: true, normalizedName: true }
    });
    const condensedNameMap = new Map<string, bigint>();
    for (const p of playersWithoutTeam) {
      const condensed = condenseName(p.normalizedName);
      if (!condensedNameMap.has(condensed)) {
        condensedNameMap.set(condensed, p.id);
      }
    }

    // Fetch ESPN teams
    const espnTeams = await fetchTeamsForLeague(league);
    console.log(`Found ${espnTeams.length} ESPN teams`);

    let totalUpdated = 0;
    let totalNotFound = 0;

    for (const espnTeam of espnTeams) {
      // Match ESPN team to our canonical team (with abbreviation mapping)
      const mappedAbbr = mapEspnAbbr(league, espnTeam.abbr);
      const canonicalTeam = teamByAbbr.get(mappedAbbr);
      if (!canonicalTeam) {
        console.log(`  [WARN] No canonical team for ESPN ${espnTeam.abbr} -> ${mappedAbbr} (${espnTeam.name})`);
        continue;
      }

      // Fetch roster
      const roster = await fetchRosterForTeam(league, espnTeam.id);
      if (roster.length === 0) {
        continue;
      }

      // Update canonical players
      let teamUpdated = 0;
      for (const player of roster) {
        const normalized = normalizePlayerName(player.name);
        if (!normalized) continue;

        // Try exact normalized name match first
        let updated = await prisma.canonicalPlayer.updateMany({
          where: {
            league,
            normalizedName: normalized,
            OR: [
              { teamId: null },
              { teamId: { not: canonicalTeam.id } }
            ]
          },
          data: {
            teamId: canonicalTeam.id,
            position: player.position || undefined,
          },
        });

        // If no match, try condensed name (handles hyphenated names from SGO)
        if (updated.count === 0) {
          const condensed = condenseName(player.name);
          const playerId = condensedNameMap.get(condensed);
          if (playerId) {
            await prisma.canonicalPlayer.update({
              where: { id: playerId },
              data: {
                teamId: canonicalTeam.id,
                position: player.position || undefined,
              }
            });
            condensedNameMap.delete(condensed); // Remove from map so we don't match again
            updated = { count: 1 };
          }
        }

        if (updated.count > 0) {
          teamUpdated += updated.count;
        } else {
          // Player not in our canonical table - might need to add them
          const exists = await prisma.canonicalPlayer.findUnique({
            where: { league_normalizedName: { league, normalizedName: normalized } }
          });
          if (!exists) {
            totalNotFound++;
          }
        }
      }

      if (teamUpdated > 0) {
        console.log(`  ${espnTeam.abbr}: ${teamUpdated} players updated`);
        totalUpdated += teamUpdated;
      }

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

    console.log(`\n${league.toUpperCase()} Summary: ${totalUpdated} players updated, ${totalNotFound} not in canonical table`);
  }

  // Final coverage check
  console.log('\n' + '='.repeat(60));
  console.log('FINAL COVERAGE');
  console.log('='.repeat(60) + '\n');

  for (const league of leagues) {
    const total = await prisma.canonicalPlayer.count({ where: { league } });
    const withTeam = await prisma.canonicalPlayer.count({
      where: { league, NOT: { teamId: null } }
    });
    const pct = ((withTeam / total) * 100).toFixed(1);
    console.log(`${league.toUpperCase()}: ${withTeam.toLocaleString()}/${total.toLocaleString()} (${pct}%) have teams`);
  }

  await prisma.$disconnect();
}

async function main() {
  const args = process.argv.slice(2);
  const leagueArg = args.find(a => a.startsWith('--league='))?.split('=')[1];
  const leagues = leagueArg ? [leagueArg.toLowerCase()] : ['nba', 'nfl', 'nhl', 'mlb'];

  await syncRosters(leagues);
}

main().catch((e) => {
  console.error('Sync failed:', e);
  process.exit(1);
});
