/**
 * Soccer fixture poller — one process under PM2.
 *
 * Polls the configured leagues (SOCCER_LEAGUES env, default 'WC') and
 * writes per-team `soccer:nextmatch:{team}` and `soccer:lastfixture:{team}`
 * keys to Redis. The feature extractor reads those keys on the user
 * request path; this poller is the ONLY thing that hits external APIs
 * during normal operation (the daily prefetch is the other; it owns
 * player/squad/scorer data).
 *
 * Sources per league:
 *   WC → worldcup2026 OSS API (no key, no rate limit) — `WORLDCUP_API_URL`
 *   anything else → football-data.org via the in-tree adapter
 *
 * Poll frequency:
 *   no live matches:  30 min  (POLL_INTERVAL_OFF_MS)
 *   live matches:      5 min  (POLL_INTERVAL_LIVE_MS)
 *
 * On missing API key or upstream failure: log + continue. The next tick
 * picks up where this one left off. We do not throw out of tick().
 */

const axios = require('axios');
const { cacheSet } = require('../src/utils/redis');
const fbd = require('../src/services/adapters/footballDataAdapter');

const HTTP_TIMEOUT_MS = 10_000;
const POLL_INTERVAL_OFF_MS  = 30 * 60_000;
const POLL_INTERVAL_LIVE_MS =  5 * 60_000;

// 24h TTL on fixture pointers so a stalled poller doesn't poison reads
// with old data forever. The poller refreshes on every tick.
const NEXT_MATCH_TTL_SEC  = 24 * 3600;
const LAST_FIXTURE_TTL_SEC = 7 * 24 * 3600;

const WORLDCUP_API_URL = process.env.WORLDCUP_API_URL
  || 'https://worldcup2026-api.up.railway.app/api/matches';

function parseLeagues() {
  const raw = process.env.SOCCER_LEAGUES || 'WC';
  return raw.split(',').map((s) => s.trim().toUpperCase()).filter(Boolean);
}

// Status normalization across upstream variants.
function classifyStatus(status) {
  const s = String(status || '').toUpperCase();
  if (s.includes('IN_PLAY') || s.includes('LIVE') || s.includes('PAUSED')) return 'live';
  if (s.includes('FINISHED') || s.includes('FINAL') || s.includes('COMPLETED')) return 'finished';
  return 'scheduled';
}

// Fetch WC fixtures from the OSS API. Returns the same projected shape
// as football-data adapter: { id, homeTeam, awayTeam, utcDate, status,
// score, matchday, venue, competition }.
async function fetchWorldCupFixtures() {
  try {
    const res = await axios.get(WORLDCUP_API_URL, { timeout: HTTP_TIMEOUT_MS });
    const matches = Array.isArray(res.data) ? res.data
      : Array.isArray(res.data?.matches) ? res.data.matches
      : [];
    return matches.map((m) => ({
      id: m.id ?? m.match_id ?? null,
      homeTeam: m.home_team || m.homeTeam || m.home?.name || null,
      awayTeam: m.away_team || m.awayTeam || m.away?.name || null,
      utcDate: m.utc_date || m.utcDate || m.date || null,
      status:  m.status || m.match_status || 'SCHEDULED',
      score:   m.score || null,
      matchday: m.matchday ?? m.round ?? null,
      venue:   m.venue || m.stadium || null,
      competition: 'WC',
    }));
  } catch (err) {
    console.warn('[poller-soccer] worldcup API fetch failed:', err.message);
    return null;
  }
}

// Fetch via league code through the football-data adapter (NULL when
// no key configured — the adapter handles that). For WC we prefer the
// OSS API to save football-data quota.
async function fetchLeagueFixtures(league) {
  if (league === 'WC') {
    const wc = await fetchWorldCupFixtures();
    if (wc !== null) return wc;
    // OSS down → fall back to football-data if a key is configured.
    return fbd.getWorldCupFixtures();
  }
  return fbd.getLeagueFixtures(league);
}

// Index fixtures into per-team `nextmatch` / `lastfixture` keys. Returns
// { scheduled, live, finished } counts for the tick summary.
async function indexFixturesForLeague(league, fixtures) {
  const counts = { scheduled: 0, live: 0, finished: 0 };
  if (!Array.isArray(fixtures)) return counts;

  // Sort by date so the FIRST scheduled fixture per team is "next",
  // and the LATEST finished one is "last".
  const sorted = fixtures.slice().sort((a, b) => {
    const da = Date.parse(a.utcDate || '') || 0;
    const db = Date.parse(b.utcDate || '') || 0;
    return da - db;
  });

  const now = Date.now();
  const nextByTeam = new Map();
  const lastByTeam = new Map();

  for (const f of sorted) {
    if (!f.homeTeam || !f.awayTeam) continue;
    const cls = classifyStatus(f.status);
    counts[cls] = (counts[cls] || 0) + 1;
    const ts = Date.parse(f.utcDate || '') || 0;

    if (cls === 'scheduled' && ts >= now) {
      // First-seen wins (sorted ascending → earliest).
      if (!nextByTeam.has(f.homeTeam)) {
        nextByTeam.set(f.homeTeam, {
          opponent: f.awayTeam, venue: f.venue, isHome: true,
          utcDate: f.utcDate, status: f.status, league,
          daysUntil: Math.max(0, Math.round((ts - now) / 86_400_000)),
          referee: f.referee || null,
        });
      }
      if (!nextByTeam.has(f.awayTeam)) {
        nextByTeam.set(f.awayTeam, {
          opponent: f.homeTeam, venue: f.venue, isHome: false,
          utcDate: f.utcDate, status: f.status, league,
          daysUntil: Math.max(0, Math.round((ts - now) / 86_400_000)),
          referee: f.referee || null,
        });
      }
    } else if (cls === 'finished') {
      // Latest-seen wins → overwrite on each iteration since sorted asc.
      lastByTeam.set(f.homeTeam, { utcDate: f.utcDate, opponent: f.awayTeam, isHome: true, score: f.score, league });
      lastByTeam.set(f.awayTeam, { utcDate: f.utcDate, opponent: f.homeTeam, isHome: false, score: f.score, league });
    }
  }

  // Persist. Don't block on individual failures — Redis errors fail
  // gracefully inside cacheSet.
  const writes = [];
  for (const [team, payload] of nextByTeam) {
    writes.push(cacheSet(`soccer:nextmatch:${team}`, payload, NEXT_MATCH_TTL_SEC));
  }
  for (const [team, payload] of lastByTeam) {
    writes.push(cacheSet(`soccer:lastfixture:${team}`, payload, LAST_FIXTURE_TTL_SEC));
  }
  await Promise.all(writes);
  return counts;
}

async function tick() {
  const leagues = parseLeagues();
  const summary = [];
  let liveSeen = false;

  // Session 20 — skip ticks entirely when football-data quota is
  // exhausted. The poller's fixture fetcher hits the football-data
  // adapter for any non-WC league; firing every minute against a
  // 10-req/min limit blows the budget. The tracker's per-minute
  // window auto-resets so the next minute's tick will fire.
  try {
    const quotaTracker = require('../src/services/quotaTracker');
    const { allowed, interval } = await quotaTracker.shouldThrottle('football-data');
    if (!allowed || interval === null) {
      console.log('[poller-soccer] tick skipped — football-data quota exhausted');
      return { liveSeen: false, summary: ['quota_exhausted'] };
    }
  } catch (e) {
    // Tracker is best-effort. If it crashes (Redis hiccup) we
    // proceed — the underlying adapters will surface their own
    // 429s and the gateway's degraded-mode fail-open kicks in.
    console.warn('[poller-soccer] quotaTracker check failed:', e.message);
  }

  for (const league of leagues) {
    const fixtures = await fetchLeagueFixtures(league);
    if (fixtures === null) {
      summary.push(`${league}: no_data`);
      continue;
    }
    const counts = await indexFixturesForLeague(league, fixtures);
    summary.push(`${league}: ${fixtures.length} matches (scheduled=${counts.scheduled} live=${counts.live} finished=${counts.finished})`);
    if (counts.live > 0) liveSeen = true;
  }

  console.log(`[poller-soccer] tick — ${summary.join(', ') || 'no leagues configured'}`);
  return { liveSeen, summary };
}

// Production run loop. Self-rescheduling — interval depends on whether
// any league has a live match.
async function run() {
  let stopped = false;
  process.on('SIGTERM', () => { stopped = true; });
  process.on('SIGINT',  () => { stopped = true; });

  while (!stopped) {
    let liveSeen = false;
    try {
      const result = await tick();
      liveSeen = !!result?.liveSeen;
    } catch (err) {
      console.warn('[poller-soccer] tick error (continuing):', err.message);
    }
    const interval = liveSeen ? POLL_INTERVAL_LIVE_MS : POLL_INTERVAL_OFF_MS;
    await new Promise((resolve) => setTimeout(resolve, interval));
  }
  console.log('[poller-soccer] shutting down');
}

if (require.main === module) {
  // Only run the loop when invoked directly (PM2). Importing the module
  // from tests must NOT start the loop.
  run().catch((err) => {
    console.error('[poller-soccer] fatal:', err);
    process.exit(1);
  });
}

module.exports = {
  tick,
  __internals: {
    parseLeagues,
    classifyStatus,
    fetchWorldCupFixtures,
    fetchLeagueFixtures,
    indexFixturesForLeague,
    POLL_INTERVAL_OFF_MS,
    POLL_INTERVAL_LIVE_MS,
    NEXT_MATCH_TTL_SEC,
    LAST_FIXTURE_TTL_SEC,
  },
};
