import pool from '../db';
import { getTeamAbbr } from '../lib/team-abbreviations';

const ESPN_LEAGUES: Record<string, { sport: string; league: string; groups?: string }> = {
  nba: { sport: 'basketball', league: 'nba' },
  nhl: { sport: 'hockey', league: 'nhl' },
  mlb: { sport: 'baseball', league: 'mlb' },
  ncaab: { sport: 'basketball', league: 'mens-college-basketball', groups: '50' },
  ncaaf: { sport: 'football', league: 'college-football' },
  nfl: { sport: 'football', league: 'nfl' },
  wnba: { sport: 'basketball', league: 'wnba' },
  epl: { sport: 'soccer', league: 'eng.1' },
  la_liga: { sport: 'soccer', league: 'esp.1' },
  serie_a: { sport: 'soccer', league: 'ita.1' },
  bundesliga: { sport: 'soccer', league: 'ger.1' },
  ligue_1: { sport: 'soccer', league: 'fra.1' },
  mls: { sport: 'soccer', league: 'usa.1' },
  champions_league: { sport: 'soccer', league: 'uefa.champions' },
  mma: { sport: 'mma', league: 'ufc' },
};

const ESPN_FETCH_TIMEOUT_MS = 12_000;

type CandidateResult = {
  homeTeam: string;
  awayTeam: string;
  homeScore: number;
  awayScore: number;
};

export type FinalResult = CandidateResult & {
  source: 'sportsgame' | 'espn' | 'ufcfight';
};

const espnScoreCache = new Map<string, Promise<CandidateResult[]>>();

function normForMatch(name: string | null | undefined): string {
  return (name || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/&/g, 'and')
    .replace(/['.\-,;:!?()]/g, '')
    .replace(/\s+/g, ' ')
    .trim();
}

function fuzzyTeamMatch(a: string | null | undefined, b: string | null | undefined): boolean {
  const na = normForMatch(a);
  const nb = normForMatch(b);
  if (!na || !nb) return false;
  if (na === nb) return true;
  if (na.includes(nb) || nb.includes(na)) return true;

  const wordsA = na.split(' ').slice(0, 2).join(' ');
  const wordsB = nb.split(' ').slice(0, 2).join(' ');
  if (wordsA.length >= 6 && wordsA === wordsB) return true;

  const lastA = na.split(' ').pop() || '';
  const lastB = nb.split(' ').pop() || '';
  if (lastA.length > 3 && lastA === lastB) {
    const firstA = na.split(' ')[0];
    const firstB = nb.split(' ')[0];
    if (firstA === firstB) return true;
  }

  return false;
}

function boundedEditDistance(a: string, b: string, maxDistance: number): number {
  if (Math.abs(a.length - b.length) > maxDistance) return maxDistance + 1;
  const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
  const curr = new Array<number>(b.length + 1);

  for (let i = 1; i <= a.length; i++) {
    curr[0] = i;
    let rowMin = curr[0];

    for (let j = 1; j <= b.length; j++) {
      const cost = a[i - 1] === b[j - 1] ? 0 : 1;
      curr[j] = Math.min(
        prev[j] + 1,
        curr[j - 1] + 1,
        prev[j - 1] + cost,
      );
      rowMin = Math.min(rowMin, curr[j]);
    }

    if (rowMin > maxDistance) return maxDistance + 1;
    for (let j = 0; j <= b.length; j++) prev[j] = curr[j];
  }

  return prev[b.length];
}

function nearNameMatch(a: string | null | undefined, b: string | null | undefined): boolean {
  const na = normForMatch(a);
  const nb = normForMatch(b);
  if (!na || !nb) return false;

  const wordsA = na.split(' ');
  const wordsB = nb.split(' ');
  if (wordsA.length !== wordsB.length || wordsA.length > 3) return false;

  for (let i = 0; i < wordsA.length; i++) {
    const maxDistance = wordsA[i].length >= 6 ? 2 : 1;
    if (boundedEditDistance(wordsA[i], wordsB[i], maxDistance) > maxDistance) return false;
  }

  return true;
}

function teamMatchScore(a: string | null | undefined, b: string | null | undefined): number {
  const na = normForMatch(a);
  const nb = normForMatch(b);
  if (!na || !nb) return 0;
  if (na === nb) return 1.0;
  if (nearNameMatch(a, b)) return 0.85;

  const lastA = (a || '').trim().split(/\s+/).pop()?.toLowerCase() || '';
  const lastB = (b || '').trim().split(/\s+/).pop()?.toLowerCase() || '';
  if (lastA === lastB && lastA.length > 2) return 0.9;

  if (na.includes(nb) || nb.includes(na)) return 0.8;

  const wordsA = (a || '').trim().split(/\s+/).slice(0, 2).join(' ').toLowerCase();
  const wordsB = (b || '').trim().split(/\s+/).slice(0, 2).join(' ').toLowerCase();
  if (wordsA.length >= 6 && wordsA === wordsB) return 0.75;

  return 0;
}

function buildTeamVariants(team: string | null | undefined): string[] {
  const variants = new Set<string>();
  const trimmed = (team || '').trim();
  if (trimmed) {
    variants.add(trimmed);
    const abbr = getTeamAbbr(trimmed);
    if (abbr && abbr !== '???') variants.add(abbr);
  }
  return [...variants];
}

function pickBestMatchedResult(
  results: CandidateResult[],
  homeVariants: string[],
  awayVariants: string[],
): { result: CandidateResult; swapped: boolean } | null {
  let best: { result: CandidateResult; swapped: boolean; score: number } | null = null;

  for (const result of results) {
    const directHome = Math.max(...homeVariants.map((variant) => teamMatchScore(result.homeTeam, variant)), 0);
    const directAway = Math.max(...awayVariants.map((variant) => teamMatchScore(result.awayTeam, variant)), 0);
    const reverseHome = Math.max(...homeVariants.map((variant) => teamMatchScore(result.awayTeam, variant)), 0);
    const reverseAway = Math.max(...awayVariants.map((variant) => teamMatchScore(result.homeTeam, variant)), 0);

    const direct = Math.min(directHome, directAway);
    const reversed = Math.min(reverseHome, reverseAway);
    const score = Math.max(direct, reversed);

    if (score < 0.8) continue;

    const swapped = reversed > direct;
    if (!best || score > best.score) {
      best = { result, swapped, score };
    }
  }

  return best ? { result: best.result, swapped: best.swapped } : null;
}

function parseScoreValue(value: unknown): number | null {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string' && value.trim()) {
    const parsed = Number(value);
    if (Number.isFinite(parsed)) return parsed;
  }
  return null;
}

function getEspnCompetitorName(competitor: any): string {
  return competitor?.team?.displayName
    || competitor?.team?.name
    || competitor?.athlete?.displayName
    || competitor?.athlete?.fullName
    || competitor?.athlete?.shortName
    || '';
}

function getEspnCompetitorScore(competitor: any): number | null {
  const direct = parseScoreValue(competitor?.score);
  if (direct != null) return direct;
  const lineScore = parseScoreValue(competitor?.linescores?.[0]?.value);
  if (lineScore != null) return lineScore;
  if (competitor?.winner === true) return 1;
  if (competitor?.winner === false) return 0;
  return null;
}

async function fetchSportsGameCandidates(league: string, startsAt: string | Date | null | undefined): Promise<CandidateResult[]> {
  if (!startsAt) return [];

  let result: any = { rows: [] };
  try {
    result = await pool.query(
      `SELECT "homeTeam", "awayTeam", "homeScore", "awayScore"
       FROM "SportsGame"
       WHERE league = $1
         AND "gameDate"::date >= ($2::timestamptz - interval '1 day')::date
         AND "gameDate"::date <= ($2::timestamptz + interval '1 day')::date
         AND LOWER(COALESCE("status", '')) = 'final'
         AND "homeScore" IS NOT NULL
         AND "awayScore" IS NOT NULL
       ORDER BY "gameDate" DESC
       LIMIT 300`,
      [league, startsAt],
    );
  } catch {
    result = { rows: [] };
  }

  const rows = Array.isArray(result?.rows) ? result.rows : [];

  return rows.map((row: any) => ({
    homeTeam: row.homeTeam,
    awayTeam: row.awayTeam,
    homeScore: Number(row.homeScore),
    awayScore: Number(row.awayScore),
  }));
}

async function fetchUfcFightCandidates(startsAt: string | Date | null | undefined): Promise<CandidateResult[]> {
  if (!startsAt) return [];

  let result: any = { rows: [] };
  try {
    result = await pool.query(
      `SELECT f."fighter1Name", f."fighter2Name", f.result1, f.result2
       FROM "UfcFight" f
       JOIN "UfcEvent" e ON e."eventId" = f."eventId"
       WHERE e.date::date >= ($1::timestamptz - interval '1 day')::date
         AND e.date::date <= ($1::timestamptz + interval '1 day')::date
       ORDER BY e.date DESC, f.id DESC
       LIMIT 100`,
      [startsAt],
    );
  } catch {
    result = { rows: [] };
  }

  const rows = Array.isArray(result?.rows) ? result.rows : [];

  return rows
    .map((row: any) => {
      const result1 = String(row.result1 || '').toUpperCase();
      const result2 = String(row.result2 || '').toUpperCase();
      const homeScore = result1 === 'W' ? 1 : result1 === 'D' ? 0 : 0;
      const awayScore = result2 === 'W' ? 1 : result2 === 'D' ? 0 : 0;
      return {
        homeTeam: row.fighter1Name,
        awayTeam: row.fighter2Name,
        homeScore,
        awayScore,
      };
    })
    .filter((row: CandidateResult) => row.homeTeam && row.awayTeam);
}

async function fetchEspnScoresForDate(league: string, dateStr: string): Promise<CandidateResult[]> {
  const cfg = ESPN_LEAGUES[league];
  if (!cfg) return [];

  const cacheKey = `${league}:${dateStr}`;
  if (!espnScoreCache.has(cacheKey)) {
    espnScoreCache.set(cacheKey, (async () => {
      let url = `https://site.api.espn.com/apis/site/v2/sports/${cfg.sport}/${cfg.league}/scoreboard?dates=${dateStr}&limit=500`;
      if (cfg.groups) url += `&groups=${cfg.groups}`;

      try {
        const res = await fetch(url, { signal: AbortSignal.timeout(ESPN_FETCH_TIMEOUT_MS) });
        if (!res.ok) return [];
        const data = await res.json() as any;
        const results: CandidateResult[] = [];

        for (const event of data.events || []) {
          const comp = event.competitions?.[0];
          if (!comp) continue;

          const statusName = comp.status?.type?.name || '';
          if (statusName !== 'STATUS_FINAL' && statusName !== 'STATUS_FULL_TIME') continue;

          const competitors = Array.isArray(comp.competitors) ? comp.competitors : [];
          if (competitors.length < 2) continue;

          const home = competitors.find((entry: any) => entry.homeAway === 'home') || competitors[0];
          const away = competitors.find((entry: any) => entry.homeAway === 'away') || competitors[1];

          const homeTeam = getEspnCompetitorName(home);
          const awayTeam = getEspnCompetitorName(away);
          const homeScore = getEspnCompetitorScore(home);
          const awayScore = getEspnCompetitorScore(away);

          if (!homeTeam || !awayTeam || homeScore == null || awayScore == null) continue;

          results.push({ homeTeam, awayTeam, homeScore, awayScore });
        }

        return results;
      } catch {
        return [];
      }
    })());
  }

  return espnScoreCache.get(cacheKey)!;
}

export async function findFinalResult(params: {
  league: string;
  startsAt: string | Date | null | undefined;
  homeTeam: string | null | undefined;
  awayTeam: string | null | undefined;
}): Promise<FinalResult | null> {
  const { league, startsAt, homeTeam, awayTeam } = params;
  if (!league || !startsAt || !homeTeam || !awayTeam) return null;

  const homeVariants = buildTeamVariants(homeTeam);
  const awayVariants = buildTeamVariants(awayTeam);

  const sportsGameCandidates = await fetchSportsGameCandidates(league, startsAt);
  const sportsGameMatch = pickBestMatchedResult(sportsGameCandidates, homeVariants, awayVariants);
  if (sportsGameMatch) {
    return {
      homeTeam,
      awayTeam,
      homeScore: sportsGameMatch.swapped ? sportsGameMatch.result.awayScore : sportsGameMatch.result.homeScore,
      awayScore: sportsGameMatch.swapped ? sportsGameMatch.result.homeScore : sportsGameMatch.result.awayScore,
      source: 'sportsgame',
    };
  }

  if (league === 'mma') {
    const ufcFightCandidates = await fetchUfcFightCandidates(startsAt);
    const ufcFightMatch = pickBestMatchedResult(ufcFightCandidates, homeVariants, awayVariants);
    if (ufcFightMatch) {
      return {
        homeTeam,
        awayTeam,
        homeScore: ufcFightMatch.swapped ? ufcFightMatch.result.awayScore : ufcFightMatch.result.homeScore,
        awayScore: ufcFightMatch.swapped ? ufcFightMatch.result.homeScore : ufcFightMatch.result.awayScore,
        source: 'ufcfight',
      };
    }
  }

  if (!ESPN_LEAGUES[league]) return null;

  const start = new Date(startsAt);
  const dates = [
    new Date(start.getTime() - 86400000),
    start,
    new Date(start.getTime() + 86400000),
  ];

  for (const date of dates) {
    const dateStr = date.toISOString().slice(0, 10).replace(/-/g, '');
    const espnCandidates = await fetchEspnScoresForDate(league, dateStr);
    const espnMatch = pickBestMatchedResult(espnCandidates, homeVariants, awayVariants);
    if (!espnMatch) continue;

    return {
      homeTeam,
      awayTeam,
      homeScore: espnMatch.swapped ? espnMatch.result.awayScore : espnMatch.result.homeScore,
      awayScore: espnMatch.swapped ? espnMatch.result.homeScore : espnMatch.result.awayScore,
      source: 'espn',
    };
  }

  return null;
}
