/**
 * ESPN injury parser.
 *
 *   Two data paths:
 *     1. ESPN team-injuries endpoint:
 *        https://site.api.espn.com/apis/site/v2/sports/{sport}/{league}/teams/{teamId}/injuries
 *     2. Injury info embedded in scoreboard / summary responses under
 *        events[i].competitions[0].competitors[t].injuries
 *
 *   We expose three callers:
 *     getTeamInjuries(sport, teamId) — primary fetch + cache
 *     getGameInjuries(sport, gameId, espnSummary?) — convenience reading
 *       the summary JSON the resolution path already loads, so we don't
 *       refetch
 *     isPlayerOut / getMissingStarters — derived helpers
 *
 *   Cache: Redis, 2-hour TTL — injuries can change at shootaround on
 *   game day so we deliberately don't go longer.
 */

const axios = require('axios');
const { cacheGet, cacheSet } = require('../../utils/redis');
const { createLimiter, createCircuitBreaker } = require('../../utils/rateLimiter');

const HTTP_TIMEOUT_MS = 10_000;
const CACHE_TTL_SECONDS = 2 * 60 * 60;

// ESPN's team-injuries endpoint takes a sport/league path. We resolve the
// league portion off the same SPORT_CONFIG used by the resolution poller
// rather than maintaining a parallel map.
const ESPN_BASE = 'https://site.api.espn.com/apis/site/v2/sports';
const SPORT_PATH = Object.freeze({
  nba:    'basketball/nba',
  wnba:   'basketball/wnba',
  mlb:    'baseball/mlb',
  nfl:    'football/nfl',
  nhl:    'hockey/nhl',
  ncaab:  'basketball/mens-college-basketball',
  ncaafb: 'football/college-football',
});

const limiter = createLimiter({ tokensPerInterval: 6, interval: 60_000 });
const breaker = createCircuitBreaker({ failureThreshold: 3, resetTimeout: 60_000 });

const STATUS_CANON = (status) => {
  if (!status) return 'UNKNOWN';
  const upper = String(status).toUpperCase();
  if (upper.includes('OUT')) return 'OUT';
  if (upper.includes('DOUBTFUL')) return 'DOUBTFUL';
  if (upper.includes('QUESTIONABLE')) return 'QUESTIONABLE';
  if (upper.includes('PROBABLE')) return 'PROBABLE';
  if (upper.includes('DAY-TO-DAY') || upper.includes('DAY_TO_DAY') || upper.includes('DTD')) return 'DAY_TO_DAY';
  return upper;
};

function normalizeInjuryEntry(entry) {
  // ESPN payloads vary — entries may carry the player at `.athlete` or be
  // flat with `.name` / `.id`. Try both shapes.
  const player = entry?.athlete ?? entry;
  return {
    playerId: String(player?.id ?? entry?.id ?? ''),
    playerName: player?.displayName ?? player?.fullName ?? entry?.name ?? null,
    status: STATUS_CANON(entry?.status ?? entry?.type?.description ?? entry?.details?.type),
    detail: entry?.details?.detail ?? entry?.shortComment ?? entry?.longComment ?? null,
  };
}

async function getTeamInjuries(sport, teamId) {
  const path = SPORT_PATH[sport];
  if (!path) return [];
  const cacheKey = `injuries:${sport}:${teamId}`;
  const cached = await cacheGet(cacheKey);
  if (cached) return cached;

  await limiter.waitForToken();
  try {
    const data = await breaker.call(async () => {
      const res = await axios.get(`${ESPN_BASE}/${path}/teams/${teamId}/injuries`, {
        timeout: HTTP_TIMEOUT_MS,
        validateStatus: (s) => (s >= 200 && s < 300) || s === 404,
      });
      // ESPN returns 404 for teams with no current injuries on some sports
      // — that's a clean "no injuries", not an error.
      if (res.status === 404) return { injuries: [] };
      return res.data;
    });
    const raw = data?.injuries || data?.athletes || [];
    const normalized = (Array.isArray(raw) ? raw : []).map(normalizeInjuryEntry).filter((e) => e.playerName);
    await cacheSet(cacheKey, normalized, CACHE_TTL_SECONDS);
    return normalized;
  } catch (err) {
    if (err?.code !== 'CIRCUIT_OPEN') {
      console.warn(`[injuries] fetch failed for ${sport}/${teamId}:`, err?.message);
    }
    return [];
  }
}

function extractGameInjuries(espnSummary) {
  // espnSummary is the JSON from /summary?event={id}. Some sports nest
  // injuries under competitions[0].competitors[t].injuries; others under
  // a top-level injuries[] array. We try both.
  const out = { home: [], away: [] };
  const comp = espnSummary?.header?.competitions?.[0] ?? espnSummary?.competitions?.[0];
  if (comp?.competitors) {
    for (const team of comp.competitors) {
      const bucket = team?.homeAway === 'home' ? 'home' : 'away';
      const list = team?.injuries || [];
      for (const e of list) {
        const normalized = normalizeInjuryEntry(e);
        if (normalized.playerName) out[bucket].push(normalized);
      }
    }
  }
  if (Array.isArray(espnSummary?.injuries)) {
    for (const e of espnSummary.injuries) {
      const normalized = normalizeInjuryEntry(e);
      if (!normalized.playerName) continue;
      const bucket = e?.team === 'home' ? 'home' : 'away';
      out[bucket].push(normalized);
    }
  }
  return out;
}

async function getGameInjuries(sport, gameId, espnSummary) {
  if (espnSummary) return extractGameInjuries(espnSummary);
  // Without a summary in hand, we'd need both team IDs from the scoreboard
  // — defer to the caller to pass espnSummary so we don't multiply ESPN
  // requests.
  return { home: [], away: [] };
}

async function isPlayerOut(sport, teamId, playerId) {
  const list = await getTeamInjuries(sport, teamId);
  const match = list.find((i) => i.playerId === String(playerId));
  if (!match) return false;
  return match.status === 'OUT' || match.status === 'DOUBTFUL';
}

// starterIds is an iterable of ESPN player IDs known to start for this team
// (resolved upstream from player_id_map or yesterday's box score).
async function getMissingStarters(sport, teamId, starterIds) {
  const injuries = await getTeamInjuries(sport, teamId);
  const starterSet = new Set([...starterIds].map(String));
  return injuries.filter(
    (i) => starterSet.has(i.playerId) && (i.status === 'OUT' || i.status === 'DOUBTFUL')
  );
}

module.exports = {
  getTeamInjuries,
  getGameInjuries,
  isPlayerOut,
  getMissingStarters,
  __internals: { limiter, breaker, normalizeInjuryEntry, STATUS_CANON },
};
