/**
 * api-football.com adapter — PRIMARY soccer data source (Session 9).
 *
 * The original API-Football by api-sports.io, now self-hosted (it
 * left RapidAPI). Free tier: 100 requests/day. Richest soccer data
 * we have access to: per-fixture player stats, lineups with grids,
 * referee assignments, full event timeline.
 *
 * Auth: `x-apisports-key` header. This is NOT RapidAPI — DO NOT add
 * x-rapidapi-host / x-rapidapi-key here (that's the FootApi backup
 * adapter, separate file).
 *
 * Env:
 *   API_FOOTBALL_KEY — api-sports.io API key
 *
 * Rate limit:
 *   Hard 100 req/day on the upstream side. We track usage in Redis
 *   via the `apifootball:daily_count` key (24h TTL) and stop at 90 to
 *   leave a 10-req safety margin for the daily prefetch and any
 *   ad-hoc operator pulls.
 *
 * Graceful degradation:
 *   - Missing key                → all functions return null
 *   - Daily counter exhausted    → stale-while-revalidate from Redis
 *                                  if available, else null
 *   - Upstream 4xx/5xx           → log + stale-while-revalidate
 *                                  fallback, else null
 *   Never throws.
 */

const axios = require('axios');
const { cacheGet, cacheSet } = require('../../utils/redis');
// Session 21 — gateway wrap. The adapter still keeps its own
// `apifootball:daily_count` counter (which has the SOFT_LIMIT=90
// soft-stop logic), but every successful HTTP call now also
// advances the canonical per-provider quota counter the admin
// dashboard reads from. Two counters, one truth-source: the
// gateway-tracker counter is the one the WARN/BLOCK thresholds
// fire against; the local daily_count is the legacy
// stale-while-revalidate trigger.
const gateway = require('../providerGateway');

const BASE_URL = 'https://v3.football.api-sports.io';
const HTTP_TIMEOUT_MS = 8_000;

// Per-endpoint cache TTLs (seconds). Match the upstream data
// volatility: fixtures drift fast, lineups freeze post-kickoff,
// season stats only matter day-over-day.
const TTL = Object.freeze({
  fixtures:    6 * 3600,   // 6h
  lineups:    24 * 3600,   // 24h — locked once posted
  playerStats: 24 * 3600,  // 24h — locked once final
  events:     12 * 3600,   // 12h
  player:     24 * 3600,   // 24h
  standings:  12 * 3600,   // 12h
});

const DAILY_LIMIT = 100;
const SAFETY_MARGIN = 10;
const SOFT_LIMIT = DAILY_LIMIT - SAFETY_MARGIN; // 90
const DAILY_COUNTER_KEY = 'apifootball:daily_count';
const DAILY_TTL_SEC = 24 * 3600;

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

async function readDailyCount() {
  const v = await cacheGet(DAILY_COUNTER_KEY);
  if (v == null) return 0;
  if (typeof v === 'number') return v;
  const n = Number(v);
  return Number.isFinite(n) ? n : 0;
}

async function bumpDailyCount() {
  const next = (await readDailyCount()) + 1;
  // 24h sliding window — every bump refreshes the TTL so the counter
  // doesn't expire mid-day after the first call of the morning.
  await cacheSet(DAILY_COUNTER_KEY, next, DAILY_TTL_SEC);
  return next;
}

// One central HTTP path — applies the api-sports auth, rate-limit
// check, caching, and stale-while-revalidate fallback. Returns
// parsed JSON or null. Never throws.
async function fetchWithCache(path, cacheKey, ttl) {
  // 1. Fresh cache hit.
  const fresh = await cacheGet(cacheKey);
  if (fresh !== null) return fresh;

  // 2. No API key → can't fetch. Callers degrade to null.
  if (!hasApiKey()) return null;

  // 3. Daily counter check — fall through to stale-while-revalidate.
  const used = await readDailyCount();
  if (used >= SOFT_LIMIT) {
    const stale = await cacheGet(`${cacheKey}:stale`);
    if (stale !== null) return stale;
    return null;
  }

  // 4. Network.
  try {
    const res = await gateway.fetch(
      'api-football',
      () => axios.get(`${BASE_URL}${path}`, {
        headers: { 'x-apisports-key': process.env.API_FOOTBALL_KEY },
        timeout: HTTP_TIMEOUT_MS,
      }),
      { capability: 'lineups', sport: 'soccer' },
    );
    await bumpDailyCount();
    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('[apiFootball] fetch failed:', path, err.message);
    const stale = await cacheGet(`${cacheKey}:stale`);
    if (stale !== null) return stale;
    return null;
  }
}

// ---- Public surface ----

/**
 * getFixtures — fixtures by league/season/date. World Cup is
 * league=1, season=2026.
 *
 * @param {Object} params { league, season, date? }
 * @returns {Promise<Array|null>}
 */
async function getFixtures({ league, season, date } = {}) {
  if (!league || !season) return null;
  const dateSegment = date ? `:${date}` : '';
  const query = date
    ? `?league=${league}&season=${season}&date=${date}`
    : `?league=${league}&season=${season}`;
  const data = await fetchWithCache(
    `/fixtures${query}`,
    `apifootball:fixtures:${league}:${season}${dateSegment}`,
    TTL.fixtures,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list)) return [];
  return list.map((f) => ({
    id: f.fixture?.id ?? null,
    utcDate: f.fixture?.date ?? null,
    status: f.fixture?.status?.short ?? null,
    venue: f.fixture?.venue?.name ?? null,
    referee: f.fixture?.referee ?? null,
    league: f.league?.name ?? null,
    season: f.league?.season ?? null,
    round: f.league?.round ?? null,
    homeTeam: f.teams?.home?.name ?? null,
    awayTeam: f.teams?.away?.name ?? null,
    homeTeamId: f.teams?.home?.id ?? null,
    awayTeamId: f.teams?.away?.id ?? null,
    score: f.score ?? null,
  }));
}

/**
 * getFixtureLineups — starting XI + bench, with grid positions.
 */
async function getFixtureLineups(fixtureId) {
  if (!fixtureId) return null;
  const data = await fetchWithCache(
    `/fixtures/lineups?fixture=${fixtureId}`,
    `apifootball:lineups:${fixtureId}`,
    TTL.lineups,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list)) return [];
  return list.map((side) => ({
    team: side.team?.name ?? null,
    teamId: side.team?.id ?? null,
    coach: side.coach?.name ?? null,
    formation: side.formation ?? null,
    startXI: (side.startXI || []).map((p) => ({
      id: p.player?.id ?? null,
      name: p.player?.name ?? null,
      number: p.player?.number ?? null,
      pos: p.player?.pos ?? null,
      grid: p.player?.grid ?? null,
    })),
    substitutes: (side.substitutes || []).map((p) => ({
      id: p.player?.id ?? null,
      name: p.player?.name ?? null,
      number: p.player?.number ?? null,
      pos: p.player?.pos ?? null,
    })),
  }));
}

/**
 * getFixturePlayerStats — per-player stats for a finished match.
 * THE money endpoint for grade calibration: shots, goals, passes,
 * tackles, cards, minutes, official rating.
 */
async function getFixturePlayerStats(fixtureId) {
  if (!fixtureId) return null;
  const data = await fetchWithCache(
    `/fixtures/players?fixture=${fixtureId}`,
    `apifootball:playerstats:${fixtureId}`,
    TTL.playerStats,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list)) return [];
  const out = [];
  for (const side of list) {
    const team = side.team?.name ?? null;
    for (const player of side.players || []) {
      const stats = player.statistics?.[0] || {};
      out.push({
        team,
        playerId: player.player?.id ?? null,
        name: player.player?.name ?? null,
        minutes: stats.games?.minutes ?? null,
        position: stats.games?.position ?? null,
        rating: stats.games?.rating ?? null,
        substitute: !!stats.games?.substitute,
        goals: stats.goals?.total ?? 0,
        assists: stats.goals?.assists ?? 0,
        shots_total: stats.shots?.total ?? 0,
        shots_on: stats.shots?.on ?? 0,
        passes_total: stats.passes?.total ?? 0,
        passes_accuracy: stats.passes?.accuracy ?? null,
        tackles_total: stats.tackles?.total ?? 0,
        tackles_blocks: stats.tackles?.blocks ?? 0,
        tackles_interceptions: stats.tackles?.interceptions ?? 0,
        yellow: stats.cards?.yellow ?? 0,
        red: stats.cards?.red ?? 0,
        saves: stats.goals?.saves ?? null,
      });
    }
  }
  return out;
}

/**
 * getFixtureEvents — minute-by-minute goals, cards, subs.
 */
async function getFixtureEvents(fixtureId) {
  if (!fixtureId) return null;
  const data = await fetchWithCache(
    `/fixtures/events?fixture=${fixtureId}`,
    `apifootball:events:${fixtureId}`,
    TTL.events,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list)) return [];
  return list.map((e) => ({
    minute: e.time?.elapsed ?? null,
    extra: e.time?.extra ?? null,
    team: e.team?.name ?? null,
    player: e.player?.name ?? null,
    assist: e.assist?.name ?? null,
    type: e.type ?? null,
    detail: e.detail ?? null,
    comments: e.comments ?? null,
  }));
}

/**
 * getPlayerSeasonStats — season aggregate for a single player.
 * The feature extractor reads this for goals_per_90, xG (if exposed
 * by the league response), minutes_per_game, start_rate.
 */
async function getPlayerSeasonStats(playerId, season) {
  if (!playerId || !season) return null;
  const data = await fetchWithCache(
    `/players?id=${playerId}&season=${season}`,
    `apifootball:player:${playerId}:season:${season}`,
    TTL.player,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list) || list.length === 0) return [];
  // The response is an array of { player, statistics: [perCompetition] }.
  // We collapse into a flat per-competition list — the caller picks
  // the league they care about (e.g. WC = league.id 1).
  const player = list[0].player || {};
  const stats = list[0].statistics || [];
  return stats.map((s) => ({
    playerId: player.id ?? null,
    name: player.name ?? null,
    nationality: player.nationality ?? null,
    team: s.team?.name ?? null,
    teamId: s.team?.id ?? null,
    leagueId: s.league?.id ?? null,
    leagueName: s.league?.name ?? null,
    appearances: s.games?.appearences ?? 0,  // sic — api-football typo
    lineups: s.games?.lineups ?? 0,
    minutes: s.games?.minutes ?? 0,
    position: s.games?.position ?? null,
    rating: s.games?.rating ?? null,
    goals: s.goals?.total ?? 0,
    assists: s.goals?.assists ?? 0,
    shots_total: s.shots?.total ?? 0,
    shots_on: s.shots?.on ?? 0,
    passes_total: s.passes?.total ?? 0,
    tackles_total: s.tackles?.total ?? 0,
    yellow: s.cards?.yellow ?? 0,
    red: s.cards?.red ?? 0,
    penalty_scored: s.penalty?.scored ?? 0,
    penalty_missed: s.penalty?.missed ?? 0,
  }));
}

/**
 * getStandings — league table with goals for/against per team.
 */
async function getStandings(league, season) {
  if (!league || !season) return null;
  const data = await fetchWithCache(
    `/standings?league=${league}&season=${season}`,
    `apifootball:standings:${league}:${season}`,
    TTL.standings,
  );
  if (data === null) return null;
  const list = data.response;
  if (!Array.isArray(list) || list.length === 0) return [];
  // World Cup standings are grouped (by group A, B, C…). Flatten so
  // the prefetch can compute defensive rank across the whole field.
  const out = [];
  for (const entry of list) {
    const groups = entry.league?.standings || [];
    for (const group of groups) {
      for (const row of group) {
        out.push({
          rank: row.rank ?? null,
          team: row.team?.name ?? null,
          teamId: row.team?.id ?? null,
          played: row.all?.played ?? 0,
          win: row.all?.win ?? 0,
          draw: row.all?.draw ?? 0,
          lose: row.all?.lose ?? 0,
          goalsFor: row.all?.goals?.for ?? 0,
          goalsAgainst: row.all?.goals?.against ?? 0,
          points: row.points ?? 0,
          group: row.group ?? null,
        });
      }
    }
  }
  return out;
}

module.exports = {
  getFixtures,
  getFixtureLineups,
  getFixturePlayerStats,
  getFixtureEvents,
  getPlayerSeasonStats,
  getStandings,
  hasApiKey,
  __internals: {
    BASE_URL,
    TTL,
    DAILY_LIMIT,
    SOFT_LIMIT,
    DAILY_COUNTER_KEY,
    readDailyCount,
    bumpDailyCount,
    resetCounterForTests: async () => cacheSet(DAILY_COUNTER_KEY, 0, DAILY_TTL_SEC),
  },
};
