<?php
/**
 * import_espn.php — 2nd source: ESPN public API (no key required).
 *
 * Provides what TheSportsDB free version lacks: TV CHANNELS (broadcasts), LIVE
 * score + status, and solid coverage of US sports + top international leagues,
 * with logos. Runs ON THE SERVER and saves to sport_events with source='espn'.
 *
 * Dedup: ESPN "owns" its leagues -> deletes its own (espn) and TheSportsDB (auto)
 * entries in its window with the same league name. TheSportsDB importer, in turn,
 * skips leagues already fetched by ESPN. That's why the ESPN cron runs FIRST.
 *
 * One request per league using ?dates=YYYYMMDD-YYYYMMDD (range). ~63 requests,
 * throttled + retried to avoid tripping ESPN's rate limiting.
 * Usage: GET import_espn.php[?token=XXX][&days=N]
 */

declare(strict_types=1);
require __DIR__ . '/lib/ibo.php';

ignore_user_abort(true);
@set_time_limit(0);
header('Content-Type: application/json; charset=utf-8');
ibo_nocache();

// Throttle between requests (safety pause to avoid rate-limiting/blocking by ESPN).
// The previous version had NO pause at all between ~63 sequential requests, which
// is a very likely trigger for ESPN's rate limiting / Akamai edge blocking.
const ESPN_THROTTLE_US = 300000; // 0.3s

// Retry settings for individual failed requests (transient rate-limit/network blips).
const ESPN_MAX_RETRIES   = 2;        // extra attempts per request, beyond the first
const ESPN_RETRY_BASE_US = 800000;   // 0.8s, doubles each retry (exponential backoff)

$need = trim((string) settings_get('sport_import_token', ''));
if ($need !== '' && (string) ($_GET['token'] ?? '') !== $need) {
    http_response_code(403);
    echo json_encode(['ok' => false, 'error' => 'forbidden']);
    exit;
}

$days = (int) ($_GET['days'] ?? settings_get('sport_days', '7'));
$days = max(1, min(14, $days));

// ESPN Leagues: [canonical sport, ESPN path, override name|null].
// null = uses the name provided by ESPN. The override is used where it overlaps with TheSportsDB
// (so that dedup by name works) or for a cleaner display name.
// ESPN owns ALL soccer, golf, tennis, motorsport (F1/NASCAR/IndyCar), and MMA.
$LEAGUES = [
    // USA majors
    ['baseball',   'baseball/mlb', 'MLB'],
    ['basketball', 'basketball/nba', 'NBA'],
    ['basketball', 'basketball/wnba', 'WNBA'],
    ['basketball', 'basketball/mens-college-basketball', 'NCAA Basketball'],
    ['amfootball', 'football/nfl', 'NFL'],
    ['amfootball', 'football/college-football', 'NCAA Football'],
    ['icehockey',  'hockey/nhl', 'NHL'],
    // MMA
    ['fighting', 'mma/ufc', 'UFC'],
    ['fighting', 'mma/pfl', 'PFL'],
    ['fighting', 'mma/bellator', 'Bellator'],
    // Golf / Tennis / Motorsport (ESPN)
    ['golf',       'golf/pga', 'PGA Tour'],
    ['golf',       'golf/lpga', 'LPGA'],
    ['golf',       'golf/eur', 'DP World Tour'],
    ['tennis',     'tennis/atp', 'ATP World Tour'],
    ['tennis',     'tennis/wta', 'WTA Tour'],
    ['motorsport', 'racing/f1', 'Formula 1'],
    ['motorsport', 'racing/nascar-premier', 'NASCAR Cup Series'],
    ['motorsport', 'racing/irl', 'IndyCar Series'],
    // Soccer (ESPN name except overrides)
    ['soccer', 'soccer/usa.1', 'American Major League Soccer'],
    ['soccer', 'soccer/usa.nwsl', null],
    ['soccer', 'soccer/mex.1', 'Mexican Primera League'],
    ['soccer', 'soccer/mex.2', null],
    ['soccer', 'soccer/eng.1', null], ['soccer', 'soccer/eng.2', null], ['soccer', 'soccer/eng.3', null],
    ['soccer', 'soccer/eng.fa', null], ['soccer', 'soccer/eng.league_cup', null],
    ['soccer', 'soccer/sco.1', null], ['soccer', 'soccer/ger.1', null], ['soccer', 'soccer/ger.2', null],
    ['soccer', 'soccer/ita.1', null], ['soccer', 'soccer/ita.2', null],
    ['soccer', 'soccer/esp.1', null], ['soccer', 'soccer/esp.2', null],
    ['soccer', 'soccer/fra.1', null], ['soccer', 'soccer/fra.2', null],
    ['soccer', 'soccer/ned.1', null], ['soccer', 'soccer/por.1', null], ['soccer', 'soccer/bel.1', null],
    ['soccer', 'soccer/tur.1', null], ['soccer', 'soccer/gre.1', null], ['soccer', 'soccer/den.1', null],
    ['soccer', 'soccer/aut.1', null], ['soccer', 'soccer/sui.1', null], ['soccer', 'soccer/nor.1', null],
    ['soccer', 'soccer/swe.1', 'Swedish Allsvenskan'], ['soccer', 'soccer/rus.1', null],
    ['soccer', 'soccer/arg.1', null], ['soccer', 'soccer/bra.1', null], ['soccer', 'soccer/chi.1', null],
    ['soccer', 'soccer/col.1', null], ['soccer', 'soccer/uru.1', null], ['soccer', 'soccer/chn.1', null],
    ['soccer', 'soccer/jpn.1', null], ['soccer', 'soccer/aus.1', null], ['soccer', 'soccer/ksa.1', null],
    ['soccer', 'soccer/uefa.champions', null], ['soccer', 'soccer/uefa.europa', null],
    ['soccer', 'soccer/uefa.europa.conf', null], ['soccer', 'soccer/uefa.nations', null],
    ['soccer', 'soccer/conmebol.libertadores', null], ['soccer', 'soccer/conmebol.sudamericana', null],
    ['soccer', 'soccer/concacaf.league', null], ['soccer', 'soccer/fifa.friendly', null],
    ['soccer', 'soccer/club.friendly', null],
];

// Migration: status + score columns (in addition to import_sport.php).
$cols = array_column(db()->query('PRAGMA table_info(sport_events)')->fetchAll(), 'name');
foreach (['source' => "'manual'", 'ext_id' => "''", 'category' => "''", 'home_badge' => "''",
          'away_badge' => "''", 'league_badge' => "''", 'status' => "''", 'score' => "''"] as $col => $def) {
    if (!in_array($col, $cols, true)) {
        db()->exec("ALTER TABLE sport_events ADD COLUMN $col TEXT NOT NULL DEFAULT $def");
    }
}

// Returns [array|null $json, int $httpStatus]. Falls back to file_get_contents if
// curl is unavailable, and always reports the HTTP status so callers can tell a
// real rate-limit/server error (429/5xx) apart from a transient network hiccup.
function espn_json_status(string $url): array
{
    $raw    = false;
    $status = 0;
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT => 14, CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_USERAGENT => 'Mozilla/5.0',
        ]);
        $raw    = curl_exec($ch);
        $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
    }
    if ($raw === false && ini_get('allow_url_fopen')) {
        $ctx = stream_context_create(['http' => [
            'timeout' => 14, 'header' => "User-Agent: Mozilla/5.0\r\n", 'ignore_errors' => true,
        ]]);
        $raw = @file_get_contents($url, false, $ctx);
        if (isset($http_response_header) && is_array($http_response_header) && isset($http_response_header[0])) {
            if (preg_match('#HTTP/\S+\s+(\d+)#', $http_response_header[0], $m)) {
                $status = (int) $m[1];
            }
        }
    }
    if (!is_string($raw) || $raw === '') {
        return [null, $status];
    }
    $j = json_decode($raw, true);
    return [is_array($j) ? $j : null, $status];
}

// Fetch with retry + exponential backoff on failure (null response or 429/5xx).
function espn_json_retry(string $url, int &$attemptsCounter): ?array
{
    $delay = ESPN_RETRY_BASE_US;
    for ($try = 0; $try <= ESPN_MAX_RETRIES; $try++) {
        $attemptsCounter++;
        [$j, $status] = espn_json_status($url);
        $rateLimited = $status === 429 || $status >= 500;
        if ($j !== null && !$rateLimited) {
            return $j;
        }
        if ($try < ESPN_MAX_RETRIES) {
            usleep($delay);
            $delay *= 2; // exponential backoff
        }
    }
    return null;
}

$beg      = gmdate('Ymd');
$end      = gmdate('Ymd', strtotime('+' . ($days - 1) . ' day') ?: time());
$rangeBeg = gmdate('Y-m-d');
$rangeEnd = gmdate('Y-m-d', strtotime('+' . ($days - 1) . ' day') ?: time());

$rows      = [];
$attempts  = 0;   // counts every HTTP call made, including retries
$requests  = 0;   // counts logical per-league requests attempted, for reporting/health
$hits      = 0;
$perLeague = [];
$leagueNames = [];
$firstReq = true;
foreach ($LEAGUES as [$sport, $path, $ovr]) {
    if (!$firstReq) { usleep(ESPN_THROTTLE_US); }   // Throttle anti rate-limit
    $firstReq = false;
    $requests++;
    $j = espn_json_retry("https://site.api.espn.com/apis/site/v2/sports/$path/scoreboard?dates=$beg-$end", $attempts);
    if ($j === null) { continue; }
    $hits++;
    $name = ($ovr !== null && $ovr !== '') ? $ovr : (string) ($j['leagues'][0]['name'] ?? $path);
    $leagueNames[$name] = true;
    $lb = $j['leagues'][0]['logos'][0]['href'] ?? '';
    $n  = 0;
    foreach (($j['events'] ?? []) ?: [] as $e) {
        $comp = $e['competitions'][0] ?? [];
        $home = null; $away = null;
        foreach (($comp['competitors'] ?? []) as $x) {
            if (($x['homeAway'] ?? '') === 'home') { $home = $x; }
            elseif (($x['homeAway'] ?? '') === 'away') { $away = $x; }
        }
        $iso  = (string) ($e['date'] ?? '');
        $date = substr($iso, 0, 10);
        $time = substr($iso, 11, 5);
        if ($date < $rangeBeg || $date > $rangeEnd) { continue; }

        $ch = [];
        foreach (($comp['broadcasts'] ?? []) as $b) {
            foreach (($b['names'] ?? []) as $nm) { $ch[] = $nm; }
        }
        if ($ch === []) {
            foreach (($comp['geoBroadcasts'] ?? []) as $b) {
                $nm = $b['media']['shortName'] ?? '';
                if ($nm !== '') { $ch[] = $nm; }
            }
        }
        $ch = array_values(array_unique(array_filter($ch)));

        $state  = $comp['status']['type']['state'] ?? '';
        $status = $state === 'in' ? 'live' : ($state === 'post' ? 'final' : '');

        if ($home !== null) {
            // Normal match (2 teams).
            $score = '';
            if ($state === 'in' || $state === 'post') {
                $hs = (string) ($home['score'] ?? '');
                $as = (string) ($away['score'] ?? '');
                if ($hs !== '' || $as !== '') { $score = $hs . '-' . $as; }
            }
            $rows[] = [
                'category' => $sport, 'league' => $name,
                'home' => trim((string) ($home['team']['displayName'] ?? '')),
                'away' => $away ? trim((string) ($away['team']['displayName'] ?? '')) : '',
                'date' => $date, 'time' => $time, 'channels' => implode(', ', $ch),
                'ext_id' => (string) ($e['id'] ?? ''),
                'home_badge' => (string) ($home['team']['logo'] ?? ''),
                'away_badge' => $away ? (string) ($away['team']['logo'] ?? '') : '',
                'league_badge' => (string) $lb, 'status' => $status, 'score' => $score,
            ];
        } else {
            // Individual/tournament (tennis, golf, F1…): event is the title (no home/away).
            $title = trim((string) ($e['name'] ?? $e['shortName'] ?? ''));
            if ($title === '') { continue; }
            $rows[] = [
                'category' => $sport, 'league' => $name, 'home' => $title, 'away' => '',
                'date' => $date, 'time' => $time, 'channels' => implode(', ', $ch),
                'ext_id' => (string) ($e['id'] ?? ''),
                'home_badge' => '', 'away_badge' => '', 'league_badge' => (string) $lb,
                'status' => $status, 'score' => '',
            ];
        }
        $n++;
    }
    $perLeague[$name] = $n;
}

// Guard: if almost everything failed, do not modify the database.
// Judged against logical requests (post-retry), not raw attempts, since attempts
// can include several retries per request.
if ($requests > 0 && ($hits / $requests) < 0.5) {
    echo json_encode([
        'ok'       => true,
        'replaced' => false,
        'reason'   => 'source_down',
        'requests' => $hits,
        'attempts' => $attempts,
        'note'     => 'Previous events retained; ESPN rate-limited or unreachable, will retry next run',
        'at'       => gmdate('c'),
    ]);
    exit;
}

// Replace: delete PAST events (prior to today), clear ESPN in current window + 'auto' (TheSportsDB) for the SAME leagues.
$names = array_keys($leagueNames);
$ph = implode(',', array_fill(0, count($names), '?'));
db()->beginTransaction();

// 1. Automatically delete all past events (prior to today):
db()->prepare("DELETE FROM sport_events WHERE event_date < ? AND source IN ('espn', 'auto')")
    ->execute([$rangeBeg]);

// 2. Clear current window ESPN events for refreshing:
db()->prepare("DELETE FROM sport_events WHERE source='espn' AND event_date>=? AND event_date<=?")
    ->execute([$rangeBeg, $rangeEnd]);

// 3. Clear overlapping leagues from TheSportsDB ('auto'):
if ($names !== []) {
    db()->prepare("DELETE FROM sport_events WHERE source='auto' AND event_date>=? AND event_date<=? AND league IN ($ph)")
        ->execute(array_merge([$rangeBeg, $rangeEnd], $names));
}

$ins = db()->prepare(
    "INSERT INTO sport_events(league,home,away,event_date,event_time,channels,sort,created_at,source,ext_id,category,home_badge,away_badge,league_badge,status,score)
     VALUES(?,?,?,?,?,?,?,?, 'espn', ?, ?, ?, ?, ?, ?, ?)"
);
$k = 0;
foreach ($rows as $r) {
    $ins->execute([$r['league'], $r['home'], $r['away'], $r['date'], $r['time'], $r['channels'], $k, gmdate('c'),
        $r['ext_id'], $r['category'], $r['home_badge'], $r['away_badge'], $r['league_badge'], $r['status'], $r['score']]);
    $k++;
}
db()->commit();

settings_set('sport_last_import_espn', gmdate('c'));
settings_set('sport_enabled', '1');

echo json_encode([
    'ok'       => true,
    'replaced' => true,
    'source'   => 'espn',
    'imported' => $k,
    'leagues'  => array_filter($perLeague),
    'requests' => $hits,
    'attempts' => $attempts,
    'at'       => gmdate('c'),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);