/**
 * Tank01 MLB adapter (Session 9) — live in-game stats + batter-vs-pitcher.
 *
 * The BvP endpoint is the headline new signal: pre-game, we can show
 * a batter's historical line against the starting pitcher (hits, K's,
 * total bases). This was a documented Day-1 gap.
 *
 * Same RAPID_API_KEY as the NBA adapter; different host. Free tier
 * is 1,000 req/month — we rely on cache TTLs to bound consumption.
 *
 * Env:
 *   RAPID_API_KEY     — shared RapidAPI marketplace key
 *   TANK01_MLB_HOST   — host (default `tank01-mlb-live-in-game-real-time-statistics.p.rapidapi.com`)
 */

const axios = require('axios');
const { cacheGet, cacheSet } = require('../../utils/redis');
// Session 21 — gateway wrap; see tank01NbaAdapter for the rationale.
// Same provider ID since both NBA and MLB endpoints share the
// RAPID_API_KEY quota (1,000 req/month combined).
const gateway = require('../providerGateway');

const HTTP_TIMEOUT_MS = 8_000;
const DEFAULT_HOST = 'tank01-mlb-live-in-game-real-time-statistics.p.rapidapi.com';

const TTL = Object.freeze({
  boxScoreLive:  5 * 60,
  boxScoreFinal: 24 * 3600,
  scoreboard:    1 * 3600,
  bvp:           24 * 3600, // BvP doesn't change mid-day — 24h cache is fine
  odds:          15 * 60,   // Session 23 — book-by-book game lines, 15min
});

function getHost() {
  return process.env.TANK01_MLB_HOST || DEFAULT_HOST;
}

function hasApiKey() {
  return !!process.env.RAPID_API_KEY;
}

async function fetchWithCache(path, cacheKey, ttl) {
  const fresh = await cacheGet(cacheKey);
  if (fresh !== null) return fresh;
  if (!hasApiKey()) return null;

  try {
    const host = getHost();
    const res = await gateway.fetch(
      'tank01',
      () => axios.get(`https://${host}${path}`, {
        headers: {
          'x-rapidapi-key': process.env.RAPID_API_KEY,
          'x-rapidapi-host': host,
        },
        timeout: HTTP_TIMEOUT_MS,
      }),
      { capability: 'box_scores', sport: 'mlb' },
    );
    const body = res.data;
    if (body && typeof body === 'object') {
      await cacheSet(cacheKey, body, ttl);
      await cacheSet(`${cacheKey}:stale`, body, ttl * 4);
    }
    return body;
  } catch (err) {
    console.warn('[tank01MLB] fetch failed:', path, err.message);
    const stale = await cacheGet(`${cacheKey}:stale`);
    if (stale !== null) return stale;
    return null;
  }
}

/**
 * getMLBBoxScore — per-player batting + pitching lines.
 * Status-aware TTL: same pattern as the NBA adapter (5 min live,
 * 24 h once Final).
 */
async function getMLBBoxScore(gameId) {
  if (!gameId) return null;
  const cacheKey = `tank01:mlb:boxscore:${gameId}`;
  const data = await fetchWithCache(
    `/getMLBBoxScore?gameID=${encodeURIComponent(gameId)}`,
    cacheKey,
    TTL.boxScoreLive,
  );
  if (data === null) return null;
  const body = data?.body || data;
  const isFinal = (() => {
    const s = body?.gameStatus || body?.status || '';
    return typeof s === 'string' && /final/i.test(s);
  })();
  if (isFinal) await cacheSet(cacheKey, data, TTL.boxScoreFinal);

  // Project batters + pitchers into a flat list. Tank01 splits these
  // into `playerStats.{batting,pitching}` — we tag the role so the
  // consumer can filter.
  const stats = body?.playerStats || {};
  const out = [];
  for (const [id, entry] of Object.entries(stats.batting || stats.batters || {})) {
    out.push({ role: 'batter', playerId: id, name: entry.longName || entry.name || null, team: entry.teamAbv || null, _raw: entry, _final: isFinal });
  }
  for (const [id, entry] of Object.entries(stats.pitching || stats.pitchers || {})) {
    out.push({ role: 'pitcher', playerId: id, name: entry.longName || entry.name || null, team: entry.teamAbv || null, _raw: entry, _final: isFinal });
  }
  return out;
}

/**
 * getMLBBatterVsPitcher — historical BvP matchup. The headline new
 * MLB signal: a batter's plate appearances, hits, K's, HRs, total
 * bases against a specific pitcher's career. Use ID, not name.
 */
async function getMLBBatterVsPitcher(batterId, pitcherId) {
  if (!batterId || !pitcherId) return null;
  const data = await fetchWithCache(
    `/getMLBBatterVsPitcher?batterID=${encodeURIComponent(batterId)}&pitcherID=${encodeURIComponent(pitcherId)}`,
    `tank01:mlb:bvp:${batterId}:${pitcherId}`,
    TTL.bvp,
  );
  if (data === null) return null;
  const body = data?.body || data;
  // The response shape can be either a single object or a `matchups`
  // array depending on schema version — normalize.
  if (Array.isArray(body)) {
    return body.map(projectBvP);
  }
  return projectBvP(body);
}

function projectBvP(row) {
  if (!row || typeof row !== 'object') return null;
  return {
    batterId: row.batterID ?? null,
    pitcherId: row.pitcherID ?? null,
    plateAppearances: num(row.PA ?? row.pa, 0),
    atBats: num(row.AB ?? row.ab, 0),
    hits: num(row.H ?? row.hits, 0),
    doubles: num(row['2B'] ?? row.doubles, 0),
    triples: num(row['3B'] ?? row.triples, 0),
    homeRuns: num(row.HR ?? row.homeRuns, 0),
    rbi: num(row.RBI ?? row.rbi, 0),
    walks: num(row.BB ?? row.walks, 0),
    strikeouts: num(row.SO ?? row.K ?? row.strikeouts, 0),
    avg: row.AVG ?? row.avg ?? null,
    ops: row.OPS ?? row.ops ?? null,
  };
}

function num(v, fallback = 0) {
  if (v == null || v === '') return fallback;
  const n = Number(v);
  return Number.isFinite(n) ? n : fallback;
}

/**
 * getMLBDailyScoreboard — schedule + scores for a date.
 */
async function getMLBDailyScoreboard(date) {
  if (!date) return null;
  const ymd = String(date).replace(/-/g, '');
  const data = await fetchWithCache(
    `/getMLBScoresOnly?gameDate=${ymd}`,
    `tank01:mlb:scoreboard:${ymd}`,
    TTL.scoreboard,
  );
  if (data === null) return null;
  const body = data?.body || data;
  // Body shape varies: array of games OR map keyed by gameID. Normalize to array.
  const entries = Array.isArray(body) ? body : Object.values(body || {});
  return entries.map((g) => ({
    gameId: g.gameID ?? null,
    homeTeam: g.home ?? null,
    awayTeam: g.away ?? null,
    gameTime: g.gameTime ?? null,
    gameStatus: g.gameStatus ?? null,
    homeScore: num(g.homePts, null),
    awayScore: num(g.awayPts, null),
  }));
}

/**
 * getMLBBettingOdds — Tank01's game-level odds feed (book-by-book).
 * Chrome Claude confirmed this serves LIVE moneylines, run lines, and
 * totals from bet365 / betmgm / caesars (Session 23). Separate from the
 * odds-api player-props pipeline; shares the RAPID_API_KEY quota.
 *
 * Returns the raw `body` (a map keyed by gameID, each carrying a
 * per-sportsbook odds object). The gameLines route normalizes it.
 */
async function getMLBBettingOdds(date) {
  if (!date) return null;
  const ymd = String(date).replace(/-/g, '');
  const data = await fetchWithCache(
    `/getMLBBettingOdds?gameDate=${ymd}`,
    `tank01:mlb:odds:${ymd}`,
    TTL.odds,
  );
  if (data === null) return null;
  return data?.body || data;
}

module.exports = {
  getMLBBoxScore,
  getMLBBatterVsPitcher,
  getMLBDailyScoreboard,
  getMLBBettingOdds,
  hasApiKey,
  __internals: {
    TTL,
    DEFAULT_HOST,
    getHost,
    projectBvP,
    num,
  },
};
