const { getAbbreviation } = require('./teamMap');

// Session 30 — PropLine (The-Odds-API-compatible) carries pinnacle, the
// sharp-line reference the odds-api allow-list lacked. Added so PropLine
// prop data through Pinnacle survives normalization. (bovada deliberately
// left OUT — it's the canonical "not-allowed" example in the tests, and
// VYNDR surfaces regulated US books.)
// 'thescore' = theScore Bet (PENN), successor to the defunct ESPN BET (item 7).
const ALLOWED_BOOKS = new Set(['draftkings', 'fanduel', 'betmgm', 'caesars', 'fanatics', 'bet365', 'hardrockbet', 'pointsbet', 'betrivers', 'pinnacle', 'thescore']);

const MARKET_MAP = {
  // NBA / WNBA props
  player_points: 'points',
  player_rebounds: 'rebounds',
  player_assists: 'assists',
  player_threes: 'threes',
  player_blocks: 'blocks',
  player_steals: 'steals',
  player_points_rebounds_assists: 'pra',
  player_turnovers: 'turnovers',
  // MLB props (Session 30) — The Odds API + PropLine share these market
  // keys for baseball. Without them PropLine/odds-api MLB props would
  // normalize to ZERO (MARKET_MAP previously had no baseball keys).
  // Internal stat_type names match the streaks/grading engines.
  batter_hits: 'hits',
  batter_home_runs: 'home_runs',
  batter_total_bases: 'total_bases',
  // Session 56 audit — must be 'rbi' (singular): the grade whitelists
  // (analyze/scan/validation.py), featureCache MLB_LOG_FIELD, and outcomeService
  // all key on 'rbi'. Normalizing to 'rbis' silently dropped every PropLine RBI
  // prop from grading AND settlement. The streaks/hotlist path uses its own
  // 'rbis' key built from raw MLB stats — independent of this normalizer.
  batter_rbis: 'rbi',
  batter_runs: 'runs',
  batter_stolen_bases: 'stolen_bases',
  batter_singles: 'singles',
  batter_doubles: 'doubles',
  batter_walks: 'walks',
  batter_strikeouts: 'batter_strikeouts',
  pitcher_strikeouts: 'strikeouts',
  pitcher_earned_runs: 'earned_runs',
  pitcher_hits_allowed: 'hits_allowed',
  pitcher_outs: 'outs',
  // NFL props (Session 31 audit) — defensive mapping added BEFORE NFL is
  // fully wired into the props flow, so it can't repeat the MLB silent-zero
  // bug (MARKET_MAP dropping every market → props normalize to nothing).
  // The Odds API + PropLine use the abbreviated `_yds` keys; the spec's
  // `_yards` spellings are mapped too so either form survives. Internal
  // stat_type names align with config/statFilters.js (passing_yards,
  // rushing_yards, receiving_yards, interceptions).
  player_pass_yds: 'passing_yards',
  player_pass_yards: 'passing_yards',
  player_pass_tds: 'pass_tds',
  player_pass_completions: 'pass_completions',
  player_pass_attempts: 'pass_attempts',
  player_pass_interceptions: 'interceptions',
  player_rush_yds: 'rushing_yards',
  player_rush_yards: 'rushing_yards',
  player_rush_attempts: 'rush_attempts',
  player_rush_tds: 'rush_tds',
  player_receptions: 'receptions',
  player_reception_yds: 'receiving_yards',
  player_receiving_yards: 'receiving_yards',
  player_reception_tds: 'reception_tds',
  player_anytime_td: 'anytime_td',
  player_kicking_points: 'kicking_points',
  // Soccer props — World Cup 2026 + permanent league support.
  // odds-api keys verified against soccer_fifa_world_cup market list.
  // 'assists' is shared with NBA — sport context discriminates downstream.
  player_goals: 'goals',
  player_shots_on_target: 'shots_on_target',
  player_shots: 'shots',
  player_tackles: 'tackles',
  player_cards: 'cards',
  player_corners: 'corners',
  player_saves: 'saves',
  player_goals_conceded: 'goals_conceded',
  player_passes: 'passes',
  team_clean_sheet: 'clean_sheet',

  // NHL (Session 32) — added alongside the NFL/NHL sport-key wiring so NHL
  // props don't silently normalize to zero in-season (same silent-failure
  // class as the NFL gap Session 31 closed). player_goals/player_assists
  // are shared with soccer/NBA — sport context discriminates downstream.
  player_shots_on_goal: 'shots_on_goal',
  goalie_saves: 'saves',

  // MMA / UFC (Wave 6 — combat intelligence). Game-level markets, NOT player
  // props: h2h = moneyline (per fighter), totals = round total (over/under).
  // Mapped here so combat odds don't silently normalize to zero when wired
  // through the odds-api path (same silent-failure class as the MLB/NHL gaps).
  // The combat surface parses these via combatAdapter.normalizeCombatOdds —
  // normalizeProps (this file) is player-prop-shaped and skips them, which is
  // correct: MMA carries no per-player point props on the free feed.
  h2h: 'moneyline',
  totals: 'round_total',
};

function normalizeProps(eventsWithOdds) {
  const props = [];

  for (const event of eventsWithOdds) {
    const homeTeam = getAbbreviation(event.home_team);
    const awayTeam = getAbbreviation(event.away_team);
    const gameTime = event.commence_time;

    if (!Array.isArray(event.bookmakers)) continue;

    for (const bookmaker of event.bookmakers) {
      if (!ALLOWED_BOOKS.has(bookmaker.key)) continue;

      if (!Array.isArray(bookmaker.markets)) continue;

      for (const market of bookmaker.markets) {
        const statType = MARKET_MAP[market.key];
        if (!statType) continue;

        const fetchedAt = market.last_update;
        const outcomes = market.outcomes || [];

        // Group outcomes by player+point to pair Over/Under
        const grouped = {};
        for (const outcome of outcomes) {
          if (!outcome.description || outcome.point == null) continue;
          const key = `${outcome.description}::${outcome.point}`;
          if (!grouped[key]) {
            grouped[key] = { player: outcome.description, point: outcome.point };
          }
          if (outcome.name === 'Over') {
            grouped[key].over_odds = outcome.price;
          } else if (outcome.name === 'Under') {
            grouped[key].under_odds = outcome.price;
          }
        }

        for (const entry of Object.values(grouped)) {
          // Skip if we don't have both sides
          if (entry.over_odds == null && entry.under_odds == null) continue;

          // Player-to-team resolution deferred to Feature 1.2 (roster data)
          props.push({
            player: entry.player,
            home_team: homeTeam,
            away_team: awayTeam,
            game_time: gameTime,
            stat_type: statType,
            book: bookmaker.key,
            line: entry.point,
            over_odds: entry.over_odds ?? null,
            under_odds: entry.under_odds ?? null,
            fetched_at: fetchedAt,
          });
        }
      }
    }
  }

  return props;
}

/**
 * american → decimal payout (for "best price" selection across books).
 * +150 → 2.5, -200 → 1.5. Returns null for a non-integer/absent price.
 */
function americanToDecimal(price) {
  if (price == null || typeof price !== 'number' || !Number.isFinite(price) || price === 0) return null;
  return price > 0 ? 1 + price / 100 : 1 + 100 / Math.abs(price);
}

/**
 * Wave 2A — outrights/futures normalizer. NEW branch: outrights outcomes are
 * `{ name, price }` with NO `point` and NO Over/Under grouping, so
 * `normalizeProps` (which requires `outcome.point` + pairs Over/Under) would
 * DROP every one of them. This keeps them.
 *
 * Input: odds-api `/sports/{key}/odds?markets=outrights` events. Each event is
 * a single futures market (e.g. "MLB World Series Winner"). Output: one market
 * per event with the BEST available american price per selection across the
 * ALLOWED books (best = highest decimal payout — honest "best price you can
 * get"), plus the book that posted it for provenance.
 *
 *   [{ key: sport_key, title: sport_title, selections:
 *        [{ name, price (american int), book }] }]
 *
 * Never fabricates: an event with no outrights market / no priced outcomes
 * yields no selections; a book not on the ALLOWED list is skipped.
 */
function normalizeOutrights(events) {
  const markets = [];
  for (const event of Array.isArray(events) ? events : []) {
    const key = event.sport_key || event.id;
    if (!key) continue;
    const title = event.sport_title || key;

    // best[name] = { price, book, dec }
    const best = {};
    for (const bookmaker of Array.isArray(event.bookmakers) ? event.bookmakers : []) {
      if (!ALLOWED_BOOKS.has(bookmaker.key)) continue;
      for (const market of Array.isArray(bookmaker.markets) ? bookmaker.markets : []) {
        if (market.key !== 'outrights') continue;
        for (const outcome of market.outcomes || []) {
          if (!outcome || !outcome.name || outcome.price == null) continue;
          const dec = americanToDecimal(outcome.price);
          if (dec == null) continue;
          const cur = best[outcome.name];
          if (!cur || dec > cur.dec) {
            best[outcome.name] = { price: outcome.price, book: bookmaker.key, dec };
          }
        }
      }
    }

    const selections = Object.keys(best).map((name) => ({
      name,
      price: best[name].price,
      book: best[name].book,
    }));
    if (selections.length === 0) continue;
    markets.push({ key: String(key), title: String(title), selections });
  }
  return markets;
}

function extractSpreads(eventsWithOdds) {
  const spreads = [];

  for (const event of eventsWithOdds) {
    const homeTeam = getAbbreviation(event.home_team);
    const awayTeam = getAbbreviation(event.away_team);
    const gameTime = event.commence_time;

    if (!Array.isArray(event.bookmakers)) continue;

    for (const bookmaker of event.bookmakers) {
      if (!ALLOWED_BOOKS.has(bookmaker.key)) continue;
      if (!Array.isArray(bookmaker.markets)) continue;

      for (const market of bookmaker.markets) {
        if (market.key !== 'spreads') continue;
        const outcomes = market.outcomes || [];

        for (const outcome of outcomes) {
          if (outcome.point == null) continue;
          // Home team spread: outcome.name matches the team full name
          if (outcome.name === event.home_team) {
            spreads.push({
              home_team: homeTeam,
              away_team: awayTeam,
              game_time: gameTime,
              book: bookmaker.key,
              home_spread: outcome.point,
              fetched_at: market.last_update,
            });
            break;
          }
        }
      }
    }
  }

  return spreads;
}

module.exports = { normalizeProps, normalizeOutrights, americanToDecimal, extractSpreads, MARKET_MAP, ALLOWED_BOOKS };
