import {
  GroupedMlbMarket,
  MarketCompletenessStatus,
  MarketCompletionMethod,
  MarketSourceMapEntry,
  NormalizedMarketSide,
  NormalizedPlayerPropMarket,
  RawMlbMarketSource,
} from './mlb-market-normalizer';

const SOURCE_PRIORITY = [
  'fanduel',
  'draftkings',
  'consensus',
  'bet365',
  'fanatics',
  'betmgm',
  'caesars',
  'espnbet',
  'fliff',
  'prizepicks',
  'underdog',
  'hardrockbet',
  'sportsgameodds',
  'unknown',
] as const;

const GAP_FILL_PRIORITY = ['fanduel', 'draftkings'] as const;

function sourceRank(source: string): number {
  const rank = SOURCE_PRIORITY.indexOf(source as (typeof SOURCE_PRIORITY)[number]);
  return rank >= 0 ? rank : SOURCE_PRIORITY.length;
}

function timestampValue(value: string | null | undefined): number {
  if (!value) return 0;
  const parsed = Date.parse(value);
  return Number.isFinite(parsed) ? parsed : 0;
}

function compareEntries(a: RawMlbMarketSource, b: RawMlbMarketSource): number {
  const aRank = sourceRank(a.source);
  const bRank = sourceRank(b.source);
  if (aRank !== bRank) return aRank - bRank;

  const aHasOdds = a.odds != null ? 1 : 0;
  const bHasOdds = b.odds != null ? 1 : 0;
  if (aHasOdds !== bHasOdds) return bHasOdds - aHasOdds;

  return timestampValue(b.timestamp) - timestampValue(a.timestamp);
}

function toNormalizedSide(entry: RawMlbMarketSource | null): NormalizedMarketSide | null {
  if (!entry) return null;
  return {
    odds: entry.odds,
    impliedProb: entry.impliedProb,
    source: entry.source,
    provider: entry.provider,
    sportsbookName: entry.sportsbookName,
    sportsbookId: entry.sportsbookId,
    timestamp: entry.timestamp,
    rawMarketId: entry.rawMarketId,
  };
}

function toSourceMap(entries: RawMlbMarketSource[]): MarketSourceMapEntry[] {
  return entries
    .map((entry) => ({
      side: entry.side,
      source: entry.source,
      odds: entry.odds,
      impliedProb: entry.impliedProb,
      provider: entry.provider,
      sportsbookName: entry.sportsbookName,
      sportsbookId: entry.sportsbookId,
      timestamp: entry.timestamp,
      rawMarketId: entry.rawMarketId,
    }))
    .sort((a, b) => {
      const sideCmp = a.side.localeCompare(b.side);
      if (sideCmp !== 0) return sideCmp;
      const rankCmp = sourceRank(a.source) - sourceRank(b.source);
      if (rankCmp !== 0) return rankCmp;
      return timestampValue(b.timestamp) - timestampValue(a.timestamp);
    });
}

function selectPreferredEntry(entries: RawMlbMarketSource[], preferredSources?: ReadonlyArray<string>): RawMlbMarketSource | null {
  const filtered = entries.filter((entry) => entry.odds != null);
  if (filtered.length === 0) return null;

  if (preferredSources && preferredSources.length > 0) {
    const preferred = filtered
      .filter((entry) => preferredSources.includes(entry.source))
      .sort(compareEntries);
    if (preferred.length > 0) return preferred[0];
  }

  return [...filtered].sort(compareEntries)[0] || null;
}

function selectBestSameSourcePair(group: GroupedMlbMarket): { source: string; over: RawMlbMarketSource; under: RawMlbMarketSource } | null {
  const bySource = new Map<string, { over?: RawMlbMarketSource; under?: RawMlbMarketSource }>();

  for (const over of group.over.filter((entry) => entry.odds != null)) {
    bySource.set(over.source, { ...(bySource.get(over.source) || {}), over });
  }
  for (const under of group.under.filter((entry) => entry.odds != null)) {
    bySource.set(under.source, { ...(bySource.get(under.source) || {}), under });
  }

  const candidates = Array.from(bySource.entries())
    .filter(([, pair]) => pair.over && pair.under)
    .map(([source, pair]) => ({
      source,
      over: pair.over!,
      under: pair.under!,
    }))
    .sort((a, b) => {
      const rankCmp = sourceRank(a.source) - sourceRank(b.source);
      if (rankCmp !== 0) return rankCmp;
      const freshestA = Math.max(timestampValue(a.over.timestamp), timestampValue(a.under.timestamp));
      const freshestB = Math.max(timestampValue(b.over.timestamp), timestampValue(b.under.timestamp));
      return freshestB - freshestA;
    });

  return candidates[0] || null;
}

function choosePrimarySource(over: RawMlbMarketSource | null, under: RawMlbMarketSource | null): string | null {
  const sources = [over?.source, under?.source].filter((value): value is string => Boolean(value));
  if (sources.length === 0) return null;
  return [...sources].sort((a, b) => sourceRank(a) - sourceRank(b))[0] || null;
}

export function normalizeGroupedMlbMarket(group: GroupedMlbMarket): NormalizedPlayerPropMarket {
  const allEntries = [...group.over, ...group.under];
  const sameSourcePair = selectBestSameSourcePair(group);

  let overEntry: RawMlbMarketSource | null = null;
  let underEntry: RawMlbMarketSource | null = null;
  let completionMethod: MarketCompletionMethod = 'none';
  let completenessStatus: MarketCompletenessStatus = 'incomplete';
  let isGapFilled = false;
  let primarySource: string | null = null;

  if (sameSourcePair) {
    overEntry = sameSourcePair.over;
    underEntry = sameSourcePair.under;
    completionMethod = 'source';
    completenessStatus = 'source_complete';
    primarySource = sameSourcePair.source;
  } else {
    overEntry = selectPreferredEntry(group.over, GAP_FILL_PRIORITY) || selectPreferredEntry(group.over);
    underEntry = selectPreferredEntry(group.under, GAP_FILL_PRIORITY) || selectPreferredEntry(group.under);

    if (overEntry && underEntry) {
      completionMethod = 'multi_source';
      completenessStatus = 'multi_source_complete';
      isGapFilled = overEntry.source !== underEntry.source;
      primarySource = choosePrimarySource(overEntry, underEntry);
    }
  }

  const availableSides: Array<'over' | 'under'> = [];
  if (group.over.length > 0) availableSides.push('over');
  if (group.under.length > 0) availableSides.push('under');

  const updatedAt = allEntries.reduce<string | null>((latest, entry) => {
    if (!latest) return entry.timestamp;
    return timestampValue(entry.timestamp) > timestampValue(latest) ? entry.timestamp : latest;
  }, null);

  return {
    eventId: group.eventId,
    startsAt: group.startsAt,
    homeTeam: group.homeTeam,
    awayTeam: group.awayTeam,
    playerId: group.playerId,
    playerName: group.playerName,
    teamShort: group.teamShort,
    statType: group.statType,
    normalizedStatType: group.normalizedStatType,
    line: group.line,
    marketName: group.marketName,
    over: toNormalizedSide(overEntry),
    under: toNormalizedSide(underEntry),
    primarySource,
    completionMethod,
    completenessStatus,
    sourceMap: toSourceMap(allEntries),
    isGapFilled,
    availableSides,
    rawMarketCount: allEntries.length,
    updatedAt,
  };
}

export function normalizeGroupedMlbMarkets(groups: GroupedMlbMarket[]): NormalizedPlayerPropMarket[] {
  return groups.map(normalizeGroupedMlbMarket);
}
