<?php
/**
 * import_sport.php — Automatic MULTI-SPORT Importer (self-hosted).
 *
 * Runs ON THE PANEL SERVER: fetches real events for various sports categories from
 * TheSportsDB (free API) for the upcoming sport_days and saves them in
 * sport_events with source='auto' and their category. The app never queries TheSportsDB directly:
 * it only reads your panel's schedule (webview.php?fetch=sport). If the source goes down,
 * the page continues showing the last imported data.
 *
 * Usage: GET import_sport.php[?token=XXX][&days=N][&key=YYY]
 *   - token: required if sport_import_token setting is configured.
 *   - Schedule 1×/day via cron pointing to this URL.
 *
 * Preserves manually added events (source='manual'): only replaces 'auto' entries.
 */

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

// Allow completion even if cron/curl times out (~2min duration with throttle).
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 on the free key).
// Bumped from 0.2s -> 0.6s: the free TheSportsDB key was tripping rate limits
// at high request volume (9 categories x N days can be 60+ calls per run).
const SPORT_THROTTLE_US = 600000; // 0.6s

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

// --- Optional Token Authentication -------------------------------------------
$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;
}

// --- Configuration -----------------------------------------------------------
$key = trim((string) ($_GET['key'] ?? settings_get('sport_import_key', '3')));
if ($key === '') { $key = '3'; }                       // '3' = TheSportsDB v1 public key

// Date range to import: [from, from+count). 'days' acts as an alias for count.
$from  = max(0, min(30, (int) ($_GET['from'] ?? 0)));
// Default lowered from 7 -> 3: fewer requests per run on the free key reduces
// rate-limit failures. Override with &days=N (or &count=N) or the sport_days setting.
$count = (int) ($_GET['count'] ?? $_GET['days'] ?? settings_get('sport_days', '3'));
$count = max(1, min(14, $count));

// Categories to import: strSport from TheSportsDB => display label.
// Editable via sport_categories setting (JSON {"Basketball":"basketball",...}).
// Soccer and tennis are covered by ESPN (import_espn) with TV channels, so they are excluded here to prevent duplicates.
$defaultCats = [
    'Basketball'        => 'basketball',
    'Ice Hockey'        => 'icehockey',
    'Baseball'          => 'baseball',
    'American Football' => 'amfootball',
    'Motorsport'        => 'motorsport',
    'Fighting'          => 'fighting',
    'Rugby'             => 'rugby',
    'Cricket'           => 'cricket',
    'Volleyball'        => 'volleyball',
];
$cats = $defaultCats;
$rawCats = trim((string) settings_get('sport_categories', ''));
if ($rawCats !== '') {
    $decoded = json_decode($rawCats, true);
    if (is_array($decoded) && $decoded !== []) {
        $cats = $decoded;
    }
}

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

// Leagues already fetched by ESPN (2nd source, with TV channels/scores): skip to avoid duplicates.
$espnLeagues = [];
foreach (db()->query("SELECT DISTINCT league FROM sport_events WHERE source='espn'")->fetchAll() as $r) {
    $espnLeagues[(string) $r['league']] = true;
}

// --- HTTP GET JSON Helper (cURL if available, fallback to file_get_contents) ---
// Returns [array|null $json, int $httpStatus] so callers can distinguish a real
// rate-limit (HTTP 429) / server error from a transient network hiccup and decide
// whether a retry is worth it.
function sport_http_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        => 12,
            CURLOPT_CONNECTTIMEOUT => 8,
            CURLOPT_USERAGENT      => 'panel-sport-import/1.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' => 12, 'header' => "User-Agent: panel-sport-import/1.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).
// Keeps the throttle pause on every attempt (including retries) to stay polite
// with the upstream API.
function sport_http_json_retry(string $url, int &$attemptsCounter): ?array
{
    $delay = SPORT_RETRY_BASE_US;
    for ($try = 0; $try <= SPORT_MAX_RETRIES; $try++) {
        $attemptsCounter++;
        [$j, $status] = sport_http_json_status($url);
        $rateLimited = $status === 429 || $status >= 500;
        if ($j !== null && !$rateLimited) {
            return $j;
        }
        if ($try < SPORT_MAX_RETRIES) {
            usleep($delay);
            $delay *= 2; // exponential backoff
        }
    }
    return null;
}

// --- Fetch events by category and date range [from, from+count) ----------------
$base     = 'https://www.thesportsdb.com/api/v1/json/' . rawurlencode($key) . '/eventsday.php';
$today    = gmdate('Y-m-d');
$rangeBeg = gmdate('Y-m-d', strtotime($today . ' +' . $from . ' day') ?: time());
$rangeEnd = gmdate('Y-m-d', strtotime($today . ' +' . ($from + $count - 1) . ' day') ?: time());
$rows     = [];
$errors   = 0;
$hits     = 0;
$attempts = 0;   // now counts every HTTP call made, including retries
$requests = 0;   // counts logical (date, category) requests attempted, for reporting
$perCat   = [];
$firstReq = true;
foreach ($cats as $sportKey => $label) {
    $perCat[$label] = 0;
    for ($i = $from; $i < $from + $count; $i++) {
        $d = gmdate('Y-m-d', strtotime($today . ' +' . $i . ' day') ?: time());
        if (!$firstReq) { usleep(SPORT_THROTTLE_US); }   // Throttle anti rate-limit
        $firstReq = false;
        $requests++;
        $j = sport_http_json_retry($base . '?d=' . $d . '&s=' . rawurlencode((string) $sportKey), $attempts);
        if ($j === null) { $errors++; continue; }
        $hits++;
        foreach (($j['events'] ?? []) ?: [] as $e) {
            $home = trim((string) ($e['strHomeTeam'] ?? ''));
            $away = trim((string) ($e['strAwayTeam'] ?? ''));
            if ($home === '') {
                // Individual/non-team events (F1, Tennis, MMA): use event title.
                $home = trim((string) ($e['strEvent'] ?? ''));
                $away = '';
            }
            if ($home === '') { continue; }
            $rows[] = [
                'category'     => (string) $label,
                'league'       => trim((string) ($e['strLeague'] ?? '')),
                'home'         => $home,
                'away'         => $away,
                'date'         => trim((string) ($e['dateEvent'] ?? $d)) ?: $d,
                'time'         => substr(trim((string) ($e['strTime'] ?? '')), 0, 5), // HH:MM (UTC)
                'channels'     => trim((string) ($e['strTVStation'] ?? '')),
                'ext_id'       => (string) ($e['idEvent'] ?? ''),
                'home_badge'   => trim((string) ($e['strHomeTeamBadge'] ?? '')),
                'away_badge'   => trim((string) ($e['strAwayTeamBadge'] ?? '')),
                'league_badge' => trim((string) ($e['strLeagueBadge'] ?? '')),
            ];
            $perCat[$label]++;
        }
    }
}

// --- Guard: If execution failed significantly (rate-limit), preserve existing data ---
// Health is now judged against logical requests (post-retry), not raw attempts,
// since attempts can include several retries per request.
$healthy = $requests > 0 && ($hits / $requests) >= 0.7;
if (!$healthy) {
    echo json_encode([
        'ok'         => true,
        'replaced'   => false,
        'reason'     => 'rate_limited_or_source_down',
        'imported'   => 0,
        'attempts'   => $attempts,
        'requests'   => $hits,
        'errors'     => $errors,
        'note'       => 'Previous events retained; retry with fewer days or a premium key',
        'at'         => gmdate('c'),
    ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
    exit;
}

// --- Replace ONLY 'auto' events IN THIS RANGE (preserves manual entries and other dates) ---
$n = 0;
db()->beginTransaction();

// 1. Automatically clear past auto events:
db()->prepare("DELETE FROM sport_events WHERE source='auto' AND event_date < ?")
    ->execute([$rangeBeg]);

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

$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)
     VALUES(?,?,?,?,?,?,?,?, 'auto', ?, ?, ?, ?, ?)"
);
foreach ($rows as $r) {
    // Keep within requested date range only
    if ($r['date'] < $rangeBeg || $r['date'] > $rangeEnd) { continue; }
    // Skip leagues covered by ESPN
    if (isset($espnLeagues[$r['league']])) { continue; }
    $ins->execute([$r['league'], $r['home'], $r['away'], $r['date'], $r['time'], $r['channels'], $n, gmdate('c'), $r['ext_id'], $r['category'], $r['home_badge'], $r['away_badge'], $r['league_badge']]);
    $n++;
}
db()->commit();

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

echo json_encode([
    'ok'         => true,
    'replaced'   => true,
    'range'      => [$rangeBeg, $rangeEnd],
    'imported'   => $n,
    'categories' => array_filter($perCat),
    'attempts'   => $attempts,
    'requests'   => $hits,
    'errors'     => $errors,
    'key'        => $key,
    'at'         => gmdate('c'),
], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);