import { getTheOddsPlayerPropMarketKeyForLeague } from './player-prop-market-registry';

const THE_ODDS_API_BASE_URL = process.env.THE_ODDS_API_BASE_URL || 'https://api.the-odds-api.com/v4';
const DEFAULT_THE_ODDS_BOOKMAKERS = (
  process.env.THE_ODDS_BOOKMAKERS
  || 'fanduel,draftkings,williamhill_us,betmgm,espnbet,betrivers,fanatics'
)
  .split(',')
  .map((value) => value.trim())
  .filter(Boolean);

const LEAGUE_TO_SPORT_KEY: Record<string, string> = {
  nba: 'basketball_nba',
  wnba: 'basketball_wnba',
  ncaab: 'basketball_ncaab',
  nhl: 'icehockey_nhl',
  mlb: 'baseball_mlb',
  epl: 'soccer_epl',
  la_liga: 'soccer_spain_la_liga',
  serie_a: 'soccer_italy_serie_a',
  bundesliga: 'soccer_germany_bundesliga',
  ligue_1: 'soccer_france_ligue_one',
  champions_league: 'soccer_uefa_champs_league',
};

const BOOKMAKER_PRIORITY = new Map<string, number>([
  ['fanduel', 1],
  ['draftkings', 2],
  ['williamhill_us', 3],
  ['betmgm', 4],
  ['espnbet', 5],
  ['betrivers', 6],
  ['fanatics', 7],
]);

const TEAM_ALIAS_KEY_OVERRIDES: Record<string, string> = {
  psg: 'psg',
  parissaintgermain: 'psg',
  staderennais: 'rennes',
  rennes: 'rennes',
  stadebrestois: 'brest',
  brest: 'brest',
  athleticclub: 'athleticbilbao',
  athleticbilbao: 'athleticbilbao',
  borussiamgladbach: 'gladbach',
  borussiamonchengladbach: 'gladbach',
  fcbayernmunchen: 'bayernmunich',
  fcbayernmuenchen: 'bayernmunich',
  bayernmunich: 'bayernmunich',
  sportingcp: 'sportinglisbon',
  sportinglisbon: 'sportinglisbon',
  fcheidenheim1846: 'heidenheim',
  fcheidenheim: 'heidenheim',
  '1fcheidenheim': 'heidenheim',
  fcstpauli: 'stpauli',
  stpauli: 'stpauli',
  ogcnice: 'nice',
  nice: 'nice',
  rcstrasbourg: 'strasbourg',
  strasbourg: 'strasbourg',
  rclens: 'lens',
  lens: 'lens',
  olympiquedemarseille: 'marseille',
  olympiquemarseille: 'marseille',
  marseille: 'marseille',
  olympiquelyonnais: 'lyon',
  lyon: 'lyon',
  deportivoalaves: 'alaves',
  alaves: 'alaves',
  levanteud: 'levante',
  levante: 'levante',
  fcaugsburg: 'augsburg',
  augsburg: 'augsburg',
  hamburgersv: 'hamburg',
  hamburg: 'hamburg',
  svwerderbremen: 'werderbremen',
  werderbremen: 'werderbremen',
  vflwolfsburg: 'wolfsburg',
  wolfsburg: 'wolfsburg',
  vfbstuttgart: 'stuttgart',
  stuttgart: 'stuttgart',
  '1fckoln': 'koln',
  koln: 'koln',
  '1fcunionberlin': 'unionberlin',
  unionberlin: 'unionberlin',
  '1fsvmainz05': 'mainz05',
  mainz05: 'mainz05',
};

export interface TheOddsHistoricalEvent {
  id: string;
  sport_key: string;
  sport_title: string;
  commence_time: string;
  home_team: string;
  away_team: string;
}

export interface TheOddsCurrentEvent {
  id: string;
  sport_key: string;
  sport_title: string;
  commence_time: string;
  home_team: string;
  away_team: string;
  bookmakers: TheOddsBookmaker[];
}

export interface TheOddsMarketOutcome {
  name: string;
  description?: string | null;
  price?: number | null;
  point?: number | null;
}

export interface TheOddsBookmakerMarket {
  key: string;
  last_update?: string | null;
  outcomes?: TheOddsMarketOutcome[];
}

export interface TheOddsBookmaker {
  key: string;
  title: string;
  last_update?: string | null;
  markets?: TheOddsBookmakerMarket[];
}

export interface TheOddsMoneylineOdds {
  home: number | null;
  away: number | null;
  draw?: number | null;
}

export interface TheOddsSpreadOdds {
  home: { line: number | null; odds: number | null } | null;
  away: { line: number | null; odds: number | null } | null;
}

export interface TheOddsTotalOdds {
  over: { line: number | null; odds: number | null } | null;
  under: { line: number | null; odds: number | null } | null;
}

export interface TheOddsGameOdds {
  moneyline: TheOddsMoneylineOdds;
  spread: TheOddsSpreadOdds;
  total: TheOddsTotalOdds;
}

interface HistoricalResponse<T> {
  timestamp: string;
  previous_timestamp?: string;
  next_timestamp?: string;
  data: T;
}

export interface TheOddsEventOddsSnapshot {
  timestamp: string;
  eventId: string;
  sportKey: string;
  commenceTime: string;
  homeTeam: string;
  awayTeam: string;
  bookmakers: TheOddsBookmaker[];
}

export interface SelectedPlayerPropOutcome {
  bookmakerKey: string;
  bookmakerTitle: string;
  marketKey: string;
  playerName: string;
  direction: 'over' | 'under';
  odds: number | null;
  line: number | null;
  marketUpdatedAt: string | null;
  bookmakerUpdatedAt: string | null;
}

function normalizeToken(value: string | null | undefined): string {
  return String(value || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9]/g, '');
}

function normalizePersonName(value: string | null | undefined): string {
  return String(value || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toLowerCase()
    .replace(/[^a-z0-9 ]/g, ' ')
    .replace(/\s+/g, ' ')
    .trim();
}

function normalizeTeamMatchKey(value: string | null | undefined): string {
  const key = normalizeToken(value);
  return TEAM_ALIAS_KEY_OVERRIDES[key] || key;
}

export function teamMatchScore(a: string | null | undefined, b: string | null | undefined): number {
  const keyA = normalizeTeamMatchKey(a);
  const keyB = normalizeTeamMatchKey(b);
  if (keyA && keyB) {
    if (keyA === keyB) return 4;
    if (keyA.includes(keyB) || keyB.includes(keyA)) return 3;
  }

  const na = normalizePersonName(a);
  const nb = normalizePersonName(b);
  if (!na || !nb) return 0;
  if (na === nb) return 3;
  if (na.includes(nb) || nb.includes(na)) return 2;

  const aWords = na.split(' ');
  const bWords = nb.split(' ');
  const aTail = aWords[aWords.length - 1] || '';
  const bTail = bWords[bWords.length - 1] || '';
  if (aTail && aTail === bTail) return 1;

  return 0;
}

function playerMatchScore(targetPlayer: string, candidatePlayer: string | null | undefined): number {
  const target = normalizePersonName(targetPlayer);
  const candidate = normalizePersonName(candidatePlayer);
  if (!target || !candidate) return 0;
  if (target === candidate) return 3;
  if (candidate.includes(target) || target.includes(candidate)) return 2;

  const targetParts = target.split(' ').filter(Boolean);
  if (targetParts.length >= 2 && targetParts.every((part) => candidate.includes(part))) return 1;

  return 0;
}

function getBookmakerPriority(bookmakerKey: string | null | undefined): number {
  return BOOKMAKER_PRIORITY.get(String(bookmakerKey || '').trim().toLowerCase()) || 99;
}

function sortBookmakers(bookmakers: TheOddsBookmaker[]): TheOddsBookmaker[] {
  return [...(bookmakers || [])].sort((a, b) => {
    const priorityDelta = getBookmakerPriority(a.key) - getBookmakerPriority(b.key);
    if (priorityDelta !== 0) return priorityDelta;

    const aUpdated = a.last_update ? new Date(a.last_update).getTime() : 0;
    const bUpdated = b.last_update ? new Date(b.last_update).getTime() : 0;
    return bUpdated - aUpdated;
  });
}

function findMarket(bookmaker: TheOddsBookmaker, marketKey: string): TheOddsBookmakerMarket | null {
  return (bookmaker.markets || []).find((market) => market.key === marketKey) || null;
}

function toFiniteNumber(value: unknown): number | null {
  const num = Number(value);
  return Number.isFinite(num) ? num : null;
}

function hasCompleteMoneyline(odds: TheOddsMoneylineOdds | null | undefined): boolean {
  return odds?.home != null && odds?.away != null;
}

function hasCompleteSpread(odds: TheOddsSpreadOdds | null | undefined): boolean {
  return odds?.home?.line != null && odds?.away?.line != null;
}

function hasCompleteTotal(odds: TheOddsTotalOdds | null | undefined): boolean {
  return odds?.over?.line != null && odds?.under?.line != null;
}

function emptyGameOdds(): TheOddsGameOdds {
  return {
    moneyline: { home: null, away: null, draw: null },
    spread: { home: null, away: null },
    total: { over: null, under: null },
  };
}

function extractMoneylineFromBookmaker(bookmaker: TheOddsBookmaker, event: TheOddsCurrentEvent): TheOddsMoneylineOdds | null {
  const market = findMarket(bookmaker, 'h2h');
  if (!market?.outcomes?.length) return null;

  let home: number | null = null;
  let away: number | null = null;
  let draw: number | null = null;

  for (const outcome of market.outcomes) {
    const price = toFiniteNumber(outcome.price);
    if (normalizeToken(outcome.name) === 'draw') {
      draw = price;
      continue;
    }

    const homeScore = teamMatchScore(outcome.name, event.home_team);
    const awayScore = teamMatchScore(outcome.name, event.away_team);
    if (homeScore > 0 && homeScore >= awayScore) {
      home = price;
    } else if (awayScore > 0 && awayScore > homeScore) {
      away = price;
    }
  }

  return home != null && away != null ? { home, away, draw } : null;
}

function extractSpreadFromBookmaker(bookmaker: TheOddsBookmaker, event: TheOddsCurrentEvent): TheOddsSpreadOdds | null {
  const market = findMarket(bookmaker, 'spreads');
  if (!market?.outcomes?.length) return null;

  let home: { line: number | null; odds: number | null } | null = null;
  let away: { line: number | null; odds: number | null } | null = null;

  for (const outcome of market.outcomes) {
    const payload = {
      line: toFiniteNumber(outcome.point),
      odds: toFiniteNumber(outcome.price),
    };
    const homeScore = teamMatchScore(outcome.name, event.home_team);
    const awayScore = teamMatchScore(outcome.name, event.away_team);
    if (homeScore > 0 && homeScore >= awayScore) {
      home = payload;
    } else if (awayScore > 0 && awayScore > homeScore) {
      away = payload;
    }
  }

  return home?.line != null && away?.line != null ? { home, away } : null;
}

function extractTotalFromBookmaker(bookmaker: TheOddsBookmaker): TheOddsTotalOdds | null {
  const market = findMarket(bookmaker, 'totals');
  if (!market?.outcomes?.length) return null;

  let over: { line: number | null; odds: number | null } | null = null;
  let under: { line: number | null; odds: number | null } | null = null;

  for (const outcome of market.outcomes) {
    const payload = {
      line: toFiniteNumber(outcome.point),
      odds: toFiniteNumber(outcome.price),
    };
    const token = normalizeToken(outcome.name);
    if (token === 'over') over = payload;
    if (token === 'under') under = payload;
  }

  return over?.line != null && under?.line != null ? { over, under } : null;
}

export function extractCurrentGameOdds(event: TheOddsCurrentEvent | null | undefined): TheOddsGameOdds {
  const output = emptyGameOdds();
  if (!event) return output;

  for (const bookmaker of sortBookmakers(event.bookmakers || [])) {
    if (!hasCompleteMoneyline(output.moneyline)) {
      const moneyline = extractMoneylineFromBookmaker(bookmaker, event);
      if (moneyline) output.moneyline = moneyline;
    }

    if (!hasCompleteSpread(output.spread)) {
      const spread = extractSpreadFromBookmaker(bookmaker, event);
      if (spread) output.spread = spread;
    }

    if (!hasCompleteTotal(output.total)) {
      const total = extractTotalFromBookmaker(bookmaker);
      if (total) output.total = total;
    }

    if (hasCompleteMoneyline(output.moneyline) && hasCompleteSpread(output.spread) && hasCompleteTotal(output.total)) {
      break;
    }
  }

  return output;
}

export function matchCurrentGameOdds(
  events: TheOddsCurrentEvent[],
  params: { homeTeam: string; awayTeam: string; startsAt: string },
): TheOddsGameOdds | null {
  const matched = matchCurrentEvent(events, params);
  return matched ? extractCurrentGameOdds(matched) : null;
}

export function mergeMissingGameOdds(
  primary: {
    moneyline: { home: number | null; away: number | null };
    spread: { home: { line: number | null; odds: number | null } | null; away: { line: number | null; odds: number | null } | null };
    total: { over: { line: number | null; odds: number | null } | null; under: { line: number | null; odds: number | null } | null };
  },
  fallback?: TheOddsGameOdds | null,
): TheOddsGameOdds {
  const safeFallback = fallback || emptyGameOdds();

  return {
    moneyline: hasCompleteMoneyline(primary.moneyline)
      ? primary.moneyline
      : safeFallback.moneyline,
    spread: hasCompleteSpread(primary.spread)
      ? primary.spread
      : safeFallback.spread,
    total: hasCompleteTotal(primary.total)
      ? primary.total
      : safeFallback.total,
  };
}

function requireApiKey(): string {
  const apiKey = String(process.env.THE_ODDS_API_KEY || '').trim();
  if (!apiKey) {
    throw new Error('THE_ODDS_API_KEY is not configured');
  }
  return apiKey;
}

function buildQuery(params: Record<string, string | number | undefined | null>): string {
  const search = new URLSearchParams();
  for (const [key, value] of Object.entries(params)) {
    if (value == null || value === '') continue;
    search.set(key, String(value));
  }
  return search.toString();
}

async function fetchJson<T>(path: string, params: Record<string, string | number | undefined | null>): Promise<T> {
  const apiKey = requireApiKey();
  const url = `${THE_ODDS_API_BASE_URL}${path}?${buildQuery({ ...params, apiKey, dateFormat: 'iso' })}`;
  const response = await fetch(url, { signal: AbortSignal.timeout(20000) });
  if (!response.ok) {
    const body = await response.text().catch(() => '');
    throw new Error(`TheOddsAPI ${response.status}: ${body || response.statusText}`);
  }
  return await response.json() as T;
}

export function getTheOddsSportKey(league: string | null | undefined): string | null {
  return LEAGUE_TO_SPORT_KEY[String(league || '').trim().toLowerCase()] || null;
}

export function getTheOddsPlayerPropMarketKey(league: string | null | undefined, propStat: string | null | undefined): string | null {
  return getTheOddsPlayerPropMarketKeyForLeague(league, propStat);
}

export function hasTheOddsApiConfigured(): boolean {
  return String(process.env.THE_ODDS_API_KEY || '').trim().length > 0;
}

export async function fetchCurrentOdds(params: {
  sportKey: string;
  markets: string[];
  regions?: string[];
  bookmakers?: string[];
}): Promise<TheOddsCurrentEvent[]> {
  const response = await fetchJson<any[]>(
    `/sports/${encodeURIComponent(params.sportKey)}/odds`,
    {
      regions: (params.regions || ['us']).join(','),
      markets: params.markets.join(','),
      bookmakers: (params.bookmakers || DEFAULT_THE_ODDS_BOOKMAKERS).join(','),
      oddsFormat: 'american',
    },
  );

  return (response || []).map((event) => ({
    id: String(event?.id || ''),
    sport_key: String(event?.sport_key || ''),
    sport_title: String(event?.sport_title || ''),
    commence_time: String(event?.commence_time || ''),
    home_team: String(event?.home_team || ''),
    away_team: String(event?.away_team || ''),
    bookmakers: Array.isArray(event?.bookmakers) ? event.bookmakers : [],
  }));
}

export async function fetchHistoricalEvents(sportKey: string, snapshotTime: string): Promise<TheOddsHistoricalEvent[]> {
  const response = await fetchJson<HistoricalResponse<TheOddsHistoricalEvent[]>>(
    `/historical/sports/${encodeURIComponent(sportKey)}/events`,
    { date: snapshotTime },
  );
  return response.data || [];
}

export async function fetchHistoricalEventOdds(params: {
  sportKey: string;
  eventId: string;
  snapshotTime: string;
  markets: string[];
  bookmakers?: string[];
}): Promise<TheOddsEventOddsSnapshot> {
  const response = await fetchJson<HistoricalResponse<any>>(
    `/historical/sports/${encodeURIComponent(params.sportKey)}/events/${encodeURIComponent(params.eventId)}/odds`,
    {
      date: params.snapshotTime,
      markets: params.markets.join(','),
      bookmakers: (params.bookmakers || DEFAULT_THE_ODDS_BOOKMAKERS).join(','),
      oddsFormat: 'american',
    },
  );

  return {
    timestamp: response.timestamp,
    eventId: response.data?.id,
    sportKey: response.data?.sport_key,
    commenceTime: response.data?.commence_time,
    homeTeam: response.data?.home_team,
    awayTeam: response.data?.away_team,
    bookmakers: response.data?.bookmakers || [],
  };
}

export async function fetchCurrentEventOdds(params: {
  sportKey: string;
  eventId: string;
  markets: string[];
  bookmakers?: string[];
}): Promise<TheOddsEventOddsSnapshot> {
  const response = await fetchJson<any>(
    `/sports/${encodeURIComponent(params.sportKey)}/events/${encodeURIComponent(params.eventId)}/odds`,
    {
      markets: params.markets.join(','),
      bookmakers: (params.bookmakers || DEFAULT_THE_ODDS_BOOKMAKERS).join(','),
      oddsFormat: 'american',
    },
  );

  return {
    timestamp: String(response?.bookmakers?.find((bookmaker: any) => bookmaker?.last_update)?.last_update || response?.commence_time || ''),
    eventId: String(response?.id || ''),
    sportKey: String(response?.sport_key || ''),
    commenceTime: String(response?.commence_time || ''),
    homeTeam: String(response?.home_team || ''),
    awayTeam: String(response?.away_team || ''),
    bookmakers: Array.isArray(response?.bookmakers) ? response.bookmakers : [],
  };
}

export function matchCurrentEvent(
  events: TheOddsCurrentEvent[],
  params: { homeTeam: string; awayTeam: string; startsAt: string },
): TheOddsCurrentEvent | null {
  const targetStart = new Date(params.startsAt).getTime();
  let best: { event: TheOddsCurrentEvent; score: number; delta: number } | null = null;

  for (const event of events) {
    const homeScore = teamMatchScore(event.home_team, params.homeTeam);
    const awayScore = teamMatchScore(event.away_team, params.awayTeam);
    if (homeScore <= 0 || awayScore <= 0) continue;

    const eventStart = new Date(event.commence_time).getTime();
    const delta = Number.isFinite(targetStart) && Number.isFinite(eventStart)
      ? Math.abs(eventStart - targetStart)
      : Number.MAX_SAFE_INTEGER;
    const score = homeScore + awayScore;

    if (!best || score > best.score || (score === best.score && delta < best.delta)) {
      best = { event, score, delta };
    }
  }

  return best?.event || null;
}

export function matchHistoricalEvent(
  events: TheOddsHistoricalEvent[],
  params: { homeTeam: string; awayTeam: string; startsAt: string },
): TheOddsHistoricalEvent | null {
  const targetStart = new Date(params.startsAt).getTime();
  let best: { event: TheOddsHistoricalEvent; score: number; delta: number } | null = null;

  for (const event of events) {
    const homeScore = teamMatchScore(event.home_team, params.homeTeam);
    const awayScore = teamMatchScore(event.away_team, params.awayTeam);
    if (homeScore <= 0 || awayScore <= 0) continue;

    const eventStart = new Date(event.commence_time).getTime();
    const delta = Number.isFinite(targetStart) && Number.isFinite(eventStart)
      ? Math.abs(eventStart - targetStart)
      : Number.MAX_SAFE_INTEGER;
    const score = homeScore + awayScore;

    if (!best || score > best.score || (score === best.score && delta < best.delta)) {
      best = { event, score, delta };
    }
  }

  return best?.event || null;
}

export function selectBestPlayerPropOutcome(
  snapshot: TheOddsEventOddsSnapshot,
  params: {
    marketKey: string;
    playerName: string;
    direction: 'over' | 'under';
    targetLine?: number | null;
    preferNearestLine?: boolean;
    bookmakerKey?: string | null;
  },
): SelectedPlayerPropOutcome | null {
  const directionLabel = params.direction === 'over' ? 'over' : 'under';
  const requiredBookmakerKey = params.bookmakerKey
    ? normalizeToken(params.bookmakerKey)
    : null;
  type OutcomeCandidate = {
    outcome: SelectedPlayerPropOutcome;
    playerScore: number;
    lineDelta: number;
    bookmakerPriority: number;
  };
  let best: OutcomeCandidate | null = null;

  for (const bookmaker of snapshot.bookmakers || []) {
    if (requiredBookmakerKey && normalizeToken(bookmaker.key) !== requiredBookmakerKey) {
      continue;
    }
    const bookmakerPriority = getBookmakerPriority(bookmaker.key);
    for (const market of bookmaker.markets || []) {
      if (market.key !== params.marketKey) continue;

      for (const outcome of market.outcomes || []) {
        const outcomeDirection = normalizeToken(outcome.name) === 'over' ? 'over'
          : normalizeToken(outcome.name) === 'under' ? 'under'
          : null;
        if (!outcomeDirection || outcomeDirection !== directionLabel) continue;

        const playerScore = playerMatchScore(params.playerName, outcome.description);
        if (playerScore <= 0) continue;

        const line = outcome.point != null ? Number(outcome.point) : null;
        const odds = outcome.price != null ? Number(outcome.price) : null;
        const lineDelta = params.targetLine != null && line != null
          ? Math.abs(line - params.targetLine)
          : 0;

        const candidate: OutcomeCandidate = {
          outcome: {
            bookmakerKey: bookmaker.key,
            bookmakerTitle: bookmaker.title,
            marketKey: market.key,
            playerName: String(outcome.description || params.playerName),
            direction: directionLabel,
            odds,
            line,
            marketUpdatedAt: market.last_update || null,
            bookmakerUpdatedAt: bookmaker.last_update || null,
          },
          playerScore,
          lineDelta,
          bookmakerPriority,
        };

        if (
          !best
          || candidate.playerScore > best.playerScore
          || (
            candidate.playerScore === best.playerScore
            && params.targetLine != null
            && candidate.lineDelta < best.lineDelta
          )
          || (
            candidate.playerScore === best.playerScore
            && candidate.lineDelta === best.lineDelta
            && candidate.bookmakerPriority < best.bookmakerPriority
          )
        ) {
          best = candidate;
        }
      }
    }
  }

  return best?.outcome || null;
}
