/**
 * Team Asset Service — resolves team logos via local disk or TheSportsDB API
 *
 * Pipeline: check local disk → query TheSportsDB → record provenance
 * TheSportsDB is free (CC-BY-SA 4.0) and provides team badges.
 */

import fs from 'fs';
import path from 'path';
import crypto from 'crypto';
import { upsertLogoRecord, getLogoRecord } from '../models/logo';
import { generateAndRecordMonogram } from './monogram';

const LOGO_ROOT = path.resolve(__dirname, '../../../public/logos');

// TheSportsDB league name mapping
const TSDB_LEAGUE_MAP: Record<string, string> = {
  nba: 'NBA',
  nfl: 'NFL',
  mlb: 'MLB',
  nhl: 'NHL',
  epl: 'English Premier League',
  ncaab: 'NCAA Division I Mens Basketball',
  ncaaf: 'NCAA Division I',
  wnba: 'WNBA',
};

// SGO abbreviation overrides (same as frontend team-logos.ts)
const ABBR_FIX: Record<string, string> = {
  PHX: 'phx', GS: 'gs', GSW: 'gs', SA: 'sa', SAS: 'sa',
  NY: 'ny', NYK: 'ny', NO: 'no', NOP: 'no',
  BKN: 'bkn', WSH: 'wsh', WAS: 'wsh',
  JAX: 'jax', LV: 'lv', LAR: 'lar', LAC: 'lac',
  SF: 'sf', TB: 'tb', GB: 'gb', KC: 'kc', NE: 'ne',
  NYY: 'nyy', NYM: 'nym', CWS: 'chw', SD: 'sd', STL: 'stl',
  NJ: 'nj', LA: 'la', SJ: 'sj',
  MCI: 'mci', MAC: 'mci', MC: 'mci',
  MUN: 'mun', MU: 'mun', MANU: 'mun',
  ARS: 'ars', AVL: 'avl', AST: 'avl',
  BHA: 'bha', BRI: 'bha',
  BOU: 'bou', BRE: 'bre', CHE: 'che',
  CRY: 'cry', CP: 'cry',
  EVE: 'eve', FUL: 'ful', LEI: 'lei',
  LIV: 'liv', NEW: 'new', NU: 'new',
  NFO: 'nfo', NOT: 'nfo', NF: 'nfo',
  SOU: 'sou', TOT: 'tot', TH: 'tot',
  WHU: 'whu', WES: 'whu',
  WOL: 'wol', WW: 'wol',
  IPC: 'ipc', LUT: 'lut',
};

// PNG magic bytes
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);

// Rate limiter: 1 request per second to TheSportsDB
let lastApiCall = 0;
async function rateLimitWait(): Promise<void> {
  const now = Date.now();
  const elapsed = now - lastApiCall;
  if (elapsed < 1000) {
    await new Promise(r => setTimeout(r, 1000 - elapsed));
  }
  lastApiCall = Date.now();
}

/** Normalize an abbreviation to match our local filename convention */
export function normalizeAbbr(shortName: string): string {
  const upper = shortName.toUpperCase();
  return (ABBR_FIX[upper] || shortName).toLowerCase();
}

/** Get the logo league directory (ncaaf shares ncaab, wnba shares nba) */
export function getLogoLeague(league: string): string {
  const lg = league.toLowerCase();
  if (lg === 'ncaaf') return 'ncaab';
  if (lg === 'wnba') return 'nba';
  return lg;
}

/** Get the full filesystem path for a team logo */
export function getLogoPath(league: string, abbr: string): string {
  const logoLeague = getLogoLeague(league);
  const normalizedAbbr = normalizeAbbr(abbr);
  return path.join(LOGO_ROOT, logoLeague, `${normalizedAbbr}.png`);
}

/** Check if a logo file exists on disk */
export function logoExistsOnDisk(league: string, abbr: string): boolean {
  return fs.existsSync(getLogoPath(league, abbr));
}

/** Compute SHA-256 checksum of a file */
function sha256File(filePath: string): string {
  const data = fs.readFileSync(filePath);
  return crypto.createHash('sha256').update(data).digest('hex');
}

/** Validate that a buffer is a valid PNG within size limits */
function validatePng(buf: Buffer): { valid: boolean; reason?: string } {
  if (buf.length < 8) return { valid: false, reason: 'too small' };
  if (!buf.subarray(0, 8).equals(PNG_MAGIC)) return { valid: false, reason: 'not a PNG (bad magic bytes)' };
  if (buf.length < 1024) return { valid: false, reason: `too small (${buf.length} bytes, min 1KB)` };
  if (buf.length > 2 * 1024 * 1024) return { valid: false, reason: `too large (${(buf.length / 1024 / 1024).toFixed(1)}MB, max 2MB)` };
  return { valid: true };
}

/** Strip mascot suffixes for better TheSportsDB matching */
function stripMascot(teamName: string): string {
  // Common pattern: "Duke Blue Devils" → "Duke"
  // But keep short names intact: "USC" stays "USC"
  const words = teamName.trim().split(/\s+/);
  if (words.length <= 1) return teamName;
  // For NCAAB, try first word or first two words
  // Known mascot words to strip
  const mascotWords = new Set([
    'wildcats', 'bulldogs', 'tigers', 'eagles', 'bears', 'knights', 'warriors',
    'hawks', 'lions', 'panthers', 'cougars', 'huskies', 'devils', 'cardinals',
    'gators', 'volunteers', 'crimson', 'tide', 'buckeyes', 'wolverines', 'spartans',
    'jayhawks', 'hoosiers', 'boilermakers', 'terrapins', 'cavaliers', 'hokies',
    'seminoles', 'hurricanes', 'tar', 'heels', 'wolfpack', 'demon', 'deacons',
    'blue', 'red', 'golden', 'fighting', 'irish', 'sooners', 'longhorns',
    'aggies', 'razorbacks', 'rebels', 'commodores', 'gamecocks', 'tigers',
    'mountaineers', 'badgers', 'hawkeyes', 'cornhuskers', 'gophers',
    'illini', 'nittany', 'orangemen', 'orange', 'yellow', 'jackets',
    'owls', 'rams', 'broncos', 'mustangs', 'cowboys', 'pirates',
    'friars', 'johnnies', 'peacocks', 'gaels', 'dons', 'zags',
    'bruins', 'beavers', 'ducks', 'trojans', 'utes', 'buffaloes',
    'sun', 'state', 'scarlet',
  ]);

  // Walk from the end, stripping mascot words
  let end = words.length;
  while (end > 1 && mascotWords.has(words[end - 1].toLowerCase())) {
    end--;
  }
  if (end < words.length && end >= 1) {
    return words.slice(0, end).join(' ');
  }
  return teamName;
}

/** Query TheSportsDB for a team badge URL */
export async function searchTheSportsDB(teamName: string, league: string): Promise<{ badgeUrl: string; tsdbTeamName: string; tsdbId: string } | null> {
  const tsdbLeague = TSDB_LEAGUE_MAP[league.toLowerCase()];
  if (!tsdbLeague) return null; // MMA etc.

  await rateLimitWait();

  // Try full name first, then stripped name
  const names = [teamName];
  const stripped = stripMascot(teamName);
  if (stripped !== teamName) names.push(stripped);

  for (const name of names) {
    try {
      const url = `https://www.thesportsdb.com/api/v1/json/3/searchteams.php?t=${encodeURIComponent(name)}`;
      const resp = await fetch(url, { signal: AbortSignal.timeout(10000) });
      if (!resp.ok) continue;

      const data: any = await resp.json();
      if (!data.teams || data.teams.length === 0) continue;

      // Filter by league to avoid cross-sport collisions
      for (const team of data.teams) {
        const teamLeague = (team.strLeague || '').toLowerCase();
        const targetLeague = tsdbLeague.toLowerCase();

        // Flexible match: check if league names overlap
        if (teamLeague.includes(targetLeague) || targetLeague.includes(teamLeague) ||
            // Handle partial matches like "NBA" in "National Basketball Association"
            (league === 'nba' && teamLeague.includes('basketball')) ||
            (league === 'nfl' && teamLeague.includes('football') && !teamLeague.includes('soccer')) ||
            (league === 'mlb' && teamLeague.includes('baseball')) ||
            (league === 'nhl' && teamLeague.includes('hockey')) ||
            (league === 'epl' && (teamLeague.includes('premier league') || teamLeague.includes('english'))) ||
            (league === 'ncaab' && teamLeague.includes('ncaa')) ||
            (league === 'ncaaf' && teamLeague.includes('ncaa')) ||
            (league === 'wnba' && teamLeague.includes('wnba'))
        ) {
          const badgeUrl = team.strTeamBadge || team.strBadge;
          if (badgeUrl) {
            return {
              badgeUrl,
              tsdbTeamName: team.strTeam,
              tsdbId: team.idTeam,
            };
          }
        }
      }
    } catch (err) {
      console.error(`  TheSportsDB search failed for "${name}":`, (err as Error).message);
    }
  }

  return null;
}

// ESPN CDN sport slug mapping
const ESPN_SPORT_MAP: Record<string, string> = {
  nba: 'nba',
  nfl: 'nfl',
  mlb: 'mlb',
  nhl: 'nhl',
  epl: 'soccer',
  ncaab: 'ncaa',
  ncaaf: 'ncaa',
  wnba: 'wnba',
};

/**
 * Search ESPN for a team logo.
 * Major leagues: direct CDN URL by abbreviation.
 * NCAAB: search ESPN teams API → get ESPN team ID → CDN URL.
 */
export async function searchESPN(
  teamName: string,
  league: string,
  abbr: string
): Promise<{ imageUrl: string } | null> {
  const lg = league.toLowerCase();
  const espnSport = ESPN_SPORT_MAP[lg];
  if (!espnSport) return null;

  // For NCAAB/NCAAF, we need to search by name to get ESPN team ID
  if (lg === 'ncaab' || lg === 'ncaaf') {
    try {
      const sportPath = lg === 'ncaab'
        ? 'basketball/mens-college-basketball'
        : 'football/college-football';
      const searchUrl = `https://site.api.espn.com/apis/site/v2/sports/${sportPath}/teams?limit=5&search=${encodeURIComponent(teamName)}`;
      await rateLimitWait();
      const resp = await fetch(searchUrl, { signal: AbortSignal.timeout(10000) });
      if (!resp.ok) return null;

      const data: any = await resp.json();
      const teams = data?.sports?.[0]?.leagues?.[0]?.teams;
      if (!teams || teams.length === 0) return null;

      // Try stripped name too
      const stripped = stripMascot(teamName).toLowerCase();
      const match = teams.find((t: any) => {
        const n = (t.team?.displayName || t.team?.name || '').toLowerCase();
        const loc = (t.team?.location || '').toLowerCase();
        return n.includes(stripped) || loc.includes(stripped);
      }) || teams[0];

      const espnId = match?.team?.id;
      if (!espnId) return null;

      return { imageUrl: `https://a.espncdn.com/i/teamlogos/ncaa/500/${espnId}.png` };
    } catch (err) {
      console.error(`  ESPN NCAA search failed for "${teamName}":`, (err as Error).message);
      return null;
    }
  }

  // Major leagues: direct CDN by abbreviation
  const normalAbbr = abbr.toLowerCase();
  const cdnUrl = `https://a.espncdn.com/i/teamlogos/${espnSport}/500/${normalAbbr}.png`;

  try {
    await rateLimitWait();
    // HEAD request to verify the image exists
    const resp = await fetch(cdnUrl, { method: 'HEAD', signal: AbortSignal.timeout(10000) });
    if (resp.ok) {
      return { imageUrl: cdnUrl };
    }
    // Try uppercase abbreviation
    const upperUrl = `https://a.espncdn.com/i/teamlogos/${espnSport}/500/${abbr.toUpperCase()}.png`;
    const resp2 = await fetch(upperUrl, { method: 'HEAD', signal: AbortSignal.timeout(10000) });
    if (resp2.ok) {
      return { imageUrl: upperUrl };
    }
  } catch (err) {
    console.error(`  ESPN CDN check failed for ${abbr}:`, (err as Error).message);
  }

  return null;
}

/** Download a PNG image to a local path with atomic write */
export async function downloadLogo(imageUrl: string, destPath: string): Promise<{ success: boolean; checksum?: string; error?: string }> {
  const tmpPath = destPath + '.tmp';

  try {
    await rateLimitWait();
    const resp = await fetch(imageUrl, { signal: AbortSignal.timeout(15000) });
    if (!resp.ok) return { success: false, error: `HTTP ${resp.status}` };

    const buf = Buffer.from(await resp.arrayBuffer());
    const validation = validatePng(buf);
    if (!validation.valid) return { success: false, error: validation.reason };

    // Ensure directory exists
    const dir = path.dirname(destPath);
    if (!fs.existsSync(dir)) {
      fs.mkdirSync(dir, { recursive: true });
    }

    // Atomic write: write to .tmp then rename
    fs.writeFileSync(tmpPath, buf);
    fs.renameSync(tmpPath, destPath);

    const checksum = crypto.createHash('sha256').update(buf).digest('hex');
    return { success: true, checksum };
  } catch (err) {
    // Clean up tmp file on failure
    try { fs.unlinkSync(tmpPath); } catch {}
    return { success: false, error: (err as Error).message };
  }
}

/** Full resolution pipeline for a single team */
export async function resolveTeamLogo(
  league: string,
  abbr: string,
  teamName?: string,
  force?: boolean
): Promise<{ status: string; source: string; message: string }> {
  const normalizedAbbr = normalizeAbbr(abbr);
  const logoLeague = getLogoLeague(league);
  const filePath = getLogoPath(league, abbr);
  const relativePath = `/logos/${logoLeague}/${normalizedAbbr}.png`;

  // Check if already resolved (skip if not forced)
  if (!force) {
    const existing = await getLogoRecord(logoLeague, normalizedAbbr);
    if (existing && existing.resolution_status === 'resolved' && existing.admin_override) {
      return { status: 'skipped', source: existing.source, message: 'Admin override — not touching' };
    }
  }

  // Step 1: Check local disk
  if (fs.existsSync(filePath)) {
    const checksum = sha256File(filePath);
    await upsertLogoRecord(logoLeague, normalizedAbbr, {
      team_name: teamName || null,
      file_path: relativePath,
      file_exists: true,
      checksum_sha256: checksum,
      source: 'local_cache',
      resolution_status: 'resolved',
    });
    return { status: 'resolved', source: 'local_cache', message: 'Found on disk' };
  }

  // Step 2: Try TheSportsDB
  if (teamName && league.toLowerCase() !== 'mma') {
    const tsdb = await searchTheSportsDB(teamName, league);
    if (tsdb) {
      const download = await downloadLogo(tsdb.badgeUrl, filePath);
      if (download.success) {
        await upsertLogoRecord(logoLeague, normalizedAbbr, {
          team_name: teamName,
          file_path: relativePath,
          file_exists: true,
          checksum_sha256: download.checksum || null,
          source: 'thesportsdb',
          license: 'CC-BY-SA 4.0',
          source_url: tsdb.badgeUrl,
          resolution_status: 'resolved',
        });
        return { status: 'resolved', source: 'thesportsdb', message: `Downloaded from TheSportsDB (${tsdb.tsdbTeamName})` };
      } else {
        console.warn(`  Download failed for ${teamName}: ${download.error}`);
      }
    }
  }

  // Step 3: Try ESPN
  if (teamName && league.toLowerCase() !== 'mma') {
    const espn = await searchESPN(teamName, league, abbr);
    if (espn) {
      const download = await downloadLogo(espn.imageUrl, filePath);
      if (download.success) {
        await upsertLogoRecord(logoLeague, normalizedAbbr, {
          team_name: teamName,
          file_path: relativePath,
          file_exists: true,
          checksum_sha256: download.checksum || null,
          source: 'espn',
          license: 'Editorial use - ESPN',
          source_url: espn.imageUrl,
          resolution_status: 'resolved',
        });
        return { status: 'resolved', source: 'espn', message: `Downloaded from ESPN CDN` };
      } else {
        console.warn(`  ESPN download failed for ${teamName}: ${download.error}`);
      }
    }
  }

  // Step 4: Generate monogram badge as fallback (never fail)
  if (teamName) {
    const mono = await generateAndRecordMonogram(
      logoLeague,
      normalizedAbbr,
      teamName,
      filePath,
      relativePath
    );
    if (mono.success) {
      return { status: 'fallback', source: 'monogram', message: 'Generated monogram badge' };
    }
  }

  // Step 5: Record as failed only if we couldn't even generate a monogram
  await upsertLogoRecord(logoLeague, normalizedAbbr, {
    team_name: teamName || null,
    file_path: relativePath,
    file_exists: false,
    source: 'local_cache',
    resolution_status: 'failed',
  });

  return { status: 'failed', source: 'none', message: 'No logo found and monogram generation failed' };
}
