'use strict';

/**
 * teamService — the Team Hub payload (Session 51).
 *
 * Assembles, in one cached call, everything `/team/:abbr` needs: team meta +
 * a roster where each player carries their archetype, season stats, and tonight's
 * graded props. MLB is the real-data path (statsapi.mlb.com roster + season
 * stats); NBA/WNBA degrade to a roster built from the players graded tonight.
 *
 * Sources (all existing): mlbStatsAdapter (roster/season), archetypeService
 * (classify), the grades:{sport} snapshot cache (props + locked archetype).
 * Everything is injectable so the whole build is unit-testable with no network.
 */

const { nameKey, normalizeName } = require('../utils/playerName');

const HUB_TTL = 15 * 60; // expensive to build; 15-min cache
const ROSTER_CONCURRENCY = 8;

async function mapLimit(items, concurrency, fn) {
  const out = new Array(items.length);
  let i = 0;
  async function worker() {
    while (i < items.length) {
      const idx = i++;
      out[idx] = await fn(items[idx], idx);
    }
  }
  await Promise.all(Array.from({ length: Math.min(concurrency, items.length || 1) }, worker));
  return out;
}

const sideChar = (dir) => (String(dir || 'over').toLowerCase() === 'under' ? 'U' : 'O');
const statLabel = (s) => String(s || '').replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());

/** Index the grades cache by normalized player → { archetype, props[] }. */
function indexGradesByPlayer(grades) {
  const map = {};
  for (const g of grades || []) {
    const k = nameKey(g.player || g.player_name);
    if (!k) continue;
    if (!map[k]) map[k] = { archetype: g.archetype || null, props: [] };
    if (!map[k].archetype && g.archetype) map[k].archetype = g.archetype;
    map[k].props.push({
      stat: statLabel(g.stat_type || g.stat),
      line: g.line,
      side: sideChar(g.direction),
      grade: g.grade,
      gradedAt: g.gradedAt || null,
    });
  }
  return map;
}

/**
 * Build the Team Hub for a sport + abbreviation. Returns the payload, or null
 * for an unknown MLB team (→ 404). Never throws.
 * opts: { mlbAdapter, classify, cacheGet, cacheSet, skipCache,
 *         mapMlbHitter, mapMlbPitcher, mlbSeasonRows }
 */
async function getTeamHub(sport, abbr, opts = {}) {
  const sp = String(sport || 'mlb').toLowerCase();
  const team = String(abbr || '').toUpperCase();
  const cacheGet = opts.cacheGet || require('../utils/redis').cacheGet;
  const cacheSet = opts.cacheSet || require('../utils/redis').cacheSet;
  const classify = opts.classify || require('./archetypeService').classify;

  const cacheKey = `teamhub:${sp}:${team}`;
  if (!opts.skipCache) {
    try { const c = await cacheGet(cacheKey); if (c) return c; } catch { /* ignore */ }
  }

  let gradeIndex = {};
  try {
    const env = await cacheGet(`grades:${sp}`);
    gradeIndex = indexGradesByPlayer(env && env.grades);
  } catch { gradeIndex = {}; }

  // ── NBA/WNBA/soccer: graceful fallback from tonight's graded players ──
  // No free roster feed; build a partial roster from the snapshot grades.
  if (sp !== 'mlb') {
    const env = await cacheGet(`grades:${sp}`).catch(() => null);
    const byPlayer = {};
    for (const g of (env && env.grades) || []) {
      const disp = normalizeName(g.player || g.player_name).display || g.player || g.player_name;
      const k = nameKey(disp);
      if (!byPlayer[k]) byPlayer[k] = { player: disp, archetype: g.archetype ? { primary: g.archetype } : null, position: null, stats: [], props: [], propCount: 0 };
      byPlayer[k].props.push({ stat: statLabel(g.stat_type || g.stat), line: g.line, side: sideChar(g.direction), grade: g.grade, gradedAt: g.gradedAt || null });
    }
    const list = Object.values(byPlayer).map((p) => ({ ...p, propCount: p.props.length }));
    const hub = { team: { name: team, abbr: team, sport: sp }, roster: list, rosterSource: 'snapshot', note: list.length ? null : 'Roster unavailable — view individual players from the slate.' };
    try { await cacheSet(cacheKey, hub, HUB_TTL); } catch { /* ignore */ }
    return hub;
  }

  // ── MLB: full real roster ──
  const mlb = opts.mlbAdapter || require('./adapters/mlbStatsAdapter');
  const intel = require('./playerIntelService')._internals;
  const mapMlbHitter = opts.mapMlbHitter || intel.mapMlbHitter;
  const mapMlbPitcher = opts.mapMlbPitcher || intel.mapMlbPitcher;
  const mlbSeasonRows = opts.mlbSeasonRows || intel.mlbSeasonRows;

  const meta = await mlb.resolveTeam(team);
  if (!meta) return null; // unknown team → 404

  const roster = await mlb.getTeamRoster(meta.id);
  const players = await mapLimit(roster, ROSTER_CONCURRENCY, async (p) => {
    const group = p.position === 'P' ? 'pitching' : 'hitting';
    let classifierInput = {};
    let stats = [];
    try {
      const season = await mlb.getSeasonAverages(p.id, undefined, group);
      if (season) {
        classifierInput = group === 'pitching' ? mapMlbPitcher(season) : mapMlbHitter(season);
        stats = mlbSeasonRows(season, group);
      }
    } catch { /* best-effort */ }

    const graded = gradeIndex[nameKey(p.name)];
    let archetype = graded && graded.archetype ? { primary: graded.archetype } : null;
    if (!archetype && Object.keys(classifierInput).length) {
      const c = classify('mlb', classifierInput);
      archetype = c.primary ? { primary: c.primary.name } : null;
    }
    return {
      player: normalizeName(p.name).display || p.name,
      position: p.position,
      jersey: p.jersey,
      archetype,
      stats,
      props: graded ? graded.props : [],
      propCount: graded ? graded.props.length : 0,
    };
  });

  const hub = { team: { name: meta.name, abbr: meta.abbr, sport: 'mlb' }, roster: players, rosterSource: 'mlb-stats' };
  try { await cacheSet(cacheKey, hub, HUB_TTL); } catch { /* ignore */ }
  return hub;
}

module.exports = { getTeamHub, indexGradesByPlayer, __internals: { mapLimit, statLabel } };
