/**
 * Roster game-log loader (Session 23).
 *
 * Streaks and hot lists both need "every player's recent game log" — but
 * VYNDR caches logs per-player on demand (`gamelogs:{sport}:{player}:{n}`)
 * as the grading flow touches them. There's no roster-wide pull, and we
 * will NOT add API calls to build one (free/cheap-only session).
 *
 * So we read what's ALREADY cached, in priority order:
 *   1. A precomputed roster blob `rosterlogs:{sport}` if a prefetch wrote
 *      one (fast path — a single read).
 *   2. The per-player `gamelogs:{sport}:*` keys (NBA/WNBA: written by
 *      gameLogService during grading, with real multi-game logs).
 *   3. (Session 25) The Tank01 box-score cache `tank01:{sport}:boxscore:*`
 *      that the prefetch writes. Each key is ONE game; we aggregate by
 *      player across games into the same roster shape. This closes the
 *      key-alignment gap Session 25 traced: the prefetch wrote box scores
 *      under a key rosterLogs never read, so MLB streaks were always empty.
 *
 * Streaks need 2+ games to surface (MIN_STREAK), so a single cached game
 * day yields no streak — correct, not a bug. Coverage grows as box scores
 * accumulate across prefetch runs.
 *
 * Everything here is Redis-only (free) and defensive — any failure yields
 * an empty roster, never a throw. An empty roster is a valid state: the
 * streaks/hot-list panels simply render nothing while other layers carry
 * the slate.
 */

const { cacheGet, getRedisClient, isDegraded } = require('../utils/redis');

const SCAN_COUNT = 200;
const MAX_KEYS = 600; // safety cap so a huge cache can't stall a request

/**
 * Parse a player display name out of a gamelogs key.
 * Key shape: `gamelogs:{sport}:{playerName}:{count}` — playerName may
 * itself contain colons in theory, so split off the known head/tail.
 */
function playerFromKey(key, sport) {
  const prefix = `gamelogs:${sport}:`;
  if (!key.startsWith(prefix)) return null;
  const rest = key.slice(prefix.length);
  const lastColon = rest.lastIndexOf(':');
  if (lastColon === -1) return rest;
  return rest.slice(0, lastColon);
}

async function scanKeys(match) {
  if (isDegraded && isDegraded()) return [];
  const redis = getRedisClient();
  if (!redis || typeof redis.scan !== 'function') return [];
  const keys = [];
  let cursor = '0';
  try {
    do {
      const [next, batch] = await redis.scan(cursor, 'MATCH', match, 'COUNT', SCAN_COUNT);
      cursor = next;
      for (const k of batch) {
        if (!keys.includes(k)) keys.push(k);
        if (keys.length >= MAX_KEYS) return keys;
      }
    } while (cursor !== '0');
  } catch (err) {
    console.warn('[rosterLogs] scan failed:', err.message);
    return keys;
  }
  return keys;
}

function scanGameLogKeys(sport) {
  return scanKeys(`gamelogs:${sport}:*`);
}

// Session 25 — box-score-cache aggregation (the prefetch-alignment bridge).

// Extract the YYYYMMDD date from a `tank01:{sport}:boxscore:{gameId}` key,
// where gameId is `YYYYMMDD_AWAY@HOME`. Used to order games most-recent-first
// (streaksService counts the streak from games[0] backward).
function boxScoreKeyDate(key) {
  const m = String(key || '').match(/boxscore:(\d{8})/);
  return m ? m[1] : '0';
}

// Project one cached box-score row into a stat row the streaks/hot-list
// engines can read. NBA rows already carry pts/reb/ast/etc. at the top
// level; MLB rows keep stats under `_raw`, so we flatten that up.
function projectBoxRow(sport, row) {
  if (!row || typeof row !== 'object') return null;
  if (sport === 'mlb') {
    const raw = row._raw && typeof row._raw === 'object' ? row._raw : {};
    return { ...raw, team: row.team, playerId: row.playerId, _final: row._final };
  }
  // nba / wnba — already flat.
  return row;
}

/**
 * Aggregate cached Tank01 box scores into [{ name, playerId, team, games }].
 * One box-score key = one game; a player appearing across N cached games
 * accumulates an N-length log. Games are ordered most-recent-first by the
 * date embedded in the key.
 */
async function aggregateBoxScores(sport) {
  const keys = await scanKeys(`tank01:${sport}:boxscore:*`);
  if (keys.length === 0) return [];
  // Most-recent game first.
  keys.sort((a, b) => boxScoreKeyDate(b).localeCompare(boxScoreKeyDate(a)));

  const byPlayer = new Map();
  for (const k of keys) {
    const box = await cacheGet(k);
    if (!Array.isArray(box)) continue;
    for (const row of box) {
      const name = row?.name;
      if (!name) continue;
      const stat = projectBoxRow(sport, row);
      if (!stat) continue;
      if (!byPlayer.has(name)) {
        byPlayer.set(name, { name, playerId: row.playerId ?? null, team: row.team ?? null, games: [] });
      }
      byPlayer.get(name).games.push(stat);
    }
  }
  return Array.from(byPlayer.values());
}

/**
 * Returns [{ name, playerId, team, games }] for a sport. Dedupes players
 * (the highest game-count key wins) so one player isn't double-counted
 * across `:10` / `:20` cache variants.
 */
async function loadRosterLogs(sport) {
  const key = String(sport || '').toLowerCase();
  if (!key) return [];

  // Fast path — a prefetched roster blob.
  const blob = await cacheGet(`rosterlogs:${key}`);
  if (Array.isArray(blob) && blob.length > 0) return blob;

  // Per-player game-log keys (NBA/WNBA grading flow writes these).
  const keys = await scanGameLogKeys(key);
  const byPlayer = new Map();
  for (const k of keys) {
    const name = playerFromKey(k, key);
    if (!name) continue;
    const games = await cacheGet(k);
    if (!Array.isArray(games) || games.length === 0) continue;
    const existing = byPlayer.get(name);
    if (!existing || games.length > existing.games.length) {
      const playerId = games[0]?.playerId ?? games[0]?.player_id ?? null;
      const team = games[0]?.team ?? games[0]?.teamAbv ?? null;
      byPlayer.set(name, { name, playerId, team, games });
    }
  }
  if (byPlayer.size > 0) return Array.from(byPlayer.values());

  // Session 25 — fall back to the Tank01 box-score cache the prefetch
  // writes. Closes the key-alignment gap that left MLB streaks empty.
  return aggregateBoxScores(key);
}

module.exports = {
  loadRosterLogs,
  __internals: {
    playerFromKey, scanGameLogKeys, scanKeys,
    boxScoreKeyDate, projectBoxRow, aggregateBoxScores,
  },
};
