import { TournamentState, type LiveMatch, type Source } from '../tournament';
import { fetchEspnScoreboard, fetchEspnSummary } from './espn';
import { fetchFootballData } from '../footballData';
import { fetchSofascore } from '../sofascore';
import { setSourceMap, getSourceMap, setMatchExt, logIngest } from '../db/db';
import type { Fixture } from '../../../src/lib/types';

// The ingestion orchestrator. Replaces the old live loop: ESPN is the primary
// live + enrichment source (works from the VPS); football-data.org and SofaScore
// are fallbacks. Everything persists to SQLite; the UI reads the DB, so a blocked
// source only means staler data.

const LIVE_WINDOW_MS = 3 * 60 * 60 * 1000;
const POLL_LIVE_MS = 20_000;
const POLL_IDLE_MS = 10 * 60_000;
const ENRICH_MS = 20 * 60_000;

function dateUTC(iso: string): string {
  const d = new Date(iso);
  return `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, '0')}${String(d.getUTCDate()).padStart(2, '0')}`;
}

export function startScheduler(state: TournamentState, onChange: () => void): () => void {
  const token = process.env.FOOTBALL_DATA_TOKEN?.trim();
  const sofa = process.env.ENABLE_SOFASCORE === 'true';
  let stopped = false;
  let liveTimer: ReturnType<typeof setTimeout> | undefined;
  let enrichTimer: ReturnType<typeof setTimeout> | undefined;

  console.log(`[ingest] scheduler — espn:on football-data:${token ? 'on' : 'off'} sofascore:${sofa ? 'on' : 'off'}`);

  // ---- one-time: map every fixture to its ESPN event id (across all dates) ----
  const mapAllFixtures = async (): Promise<void> => {
    const dates = [...new Set(state.allFixtures().map((f) => dateUTC(f.kickoff)))];
    let mapped = 0;
    for (const date of dates) {
      if (stopped) return;
      const t0 = Date.now();
      try {
        const evs = await fetchEspnScoreboard(date);
        for (const m of evs) {
          const num = state.matchFixtureNum(m.home, m.away, 'group', m.kickoff);
          if (num) { setSourceMap(num, 'espn', m.eventId); mapped++; }
        }
        logIngest('espn', 'map', true, Date.now() - t0, `${date}: ${evs.length} events`);
      } catch (e) {
        logIngest('espn', 'map', false, Date.now() - t0, e instanceof Error ? e.message : String(e));
      }
    }
    console.log(`[ingest] mapped ${mapped} fixtures to ESPN event ids`);
  };

  // ---- live scoreboard ----
  const liveTick = async (): Promise<void> => {
    const t0 = Date.now();
    let matches: LiveMatch[] = [];
    let source: Source = 'seed';
    try {
      const espn = await fetchEspnScoreboard();
      matches = espn;
      source = 'espn';
      for (const m of espn) {
        const num = state.matchFixtureNum(m.home, m.away, 'group', m.kickoff);
        if (num) setSourceMap(num, 'espn', m.eventId);
      }
      logIngest('espn', 'scoreboard', true, Date.now() - t0, `${espn.length} events`);
    } catch (e) {
      logIngest('espn', 'scoreboard', false, Date.now() - t0, e instanceof Error ? e.message : String(e));
    }
    if (!matches.length && token) {
      try { matches = await fetchFootballData(token); source = 'football-data'; } catch { /* logged via health */ }
    }
    if (!matches.length && sofa) {
      try { matches = await fetchSofascore(); source = 'sofascore'; } catch { /* expected block */ }
    }
    if (matches.length) {
      const changed = state.mergeLive(matches, source);
      if (changed.length) { console.log(`[ingest] ${changed.length} fixture(s) updated via ${source}`); onChange(); }
    }
  };

  // ---- enrichment: ESPN summary for imminent/live fixtures ----
  const enrichTick = async (): Promise<void> => {
    const now = Date.now();
    const relevant = state.allFixtures().filter((f) => {
      const k = new Date(f.kickoff).getTime();
      return f.status === 'live' || (k > now - LIVE_WINDOW_MS && k < now + 3 * 24 * 60 * 60 * 1000);
    });
    for (const f of relevant) {
      if (stopped) return;
      const eid = getSourceMap(f.num, 'espn');
      if (!eid) continue;
      const t0 = Date.now();
      try {
        const summary = await fetchEspnSummary(eid);
        setMatchExt(f.num, summary);
        logIngest('espn', 'summary', true, Date.now() - t0, `M${f.num}`);
      } catch (e) {
        logIngest('espn', 'summary', false, Date.now() - t0, e instanceof Error ? e.message : String(e));
      }
    }
  };

  const scheduleLive = (): void => {
    if (stopped) return;
    const delay = state.hasLiveActivity(LIVE_WINDOW_MS) ? POLL_LIVE_MS : POLL_IDLE_MS;
    liveTimer = setTimeout(async () => { await liveTick().catch(() => {}); scheduleLive(); }, delay);
  };
  const scheduleEnrich = (): void => {
    if (stopped) return;
    enrichTimer = setTimeout(async () => { await enrichTick().catch(() => {}); scheduleEnrich(); }, ENRICH_MS);
  };

  // boot: map ids, then run the loops
  void (async () => {
    await mapAllFixtures().catch(() => {});
    await liveTick().catch(() => {});
    await enrichTick().catch(() => {});
    scheduleLive();
    scheduleEnrich();
  })();

  return () => {
    stopped = true;
    if (liveTimer) clearTimeout(liveTimer);
    if (enrichTimer) clearTimeout(enrichTimer);
  };
}
