<?php
/**
 * sports-fixtures.php — Sports fixtures browser API (panel-side proxy)
 *
 * Called by the app at: {panel_url}api/sports-fixtures.php?action=...
 * (see ca.krankhaus.tivi.fixtures.FixturesConfig / FixturesApiClient)
 *
 * Sources fixtures from ESPN's MOBILE scoreboard endpoint
 * (site.web.api.espn.com) with an iPhone User-Agent. The desktop ESPN API
 * and TheSportsDB are both BLOCKED on shared hosting (Cloudflare 403), but
 * the mobile endpoint works and returns all sports. The panel proxies and
 * caches every request so devices never talk to ESPN directly.
 *
 * Actions (all GET):
 *   ?action=config              -> { enabled, title }
 *                                   Mirrors the shape of sports.php / webview.php
 *                                   so the sidebar menu row can be shown/hidden
 *                                   the same way.
 *   ?action=leagues             -> { success, leagues: [{id, name, sport}] }
 *                                   Every league we track, UK first, then
 *                                   Europe, then international + USA. Leagues
 *                                   with no fixtures today are still listed so
 *                                   tomorrow's events show up automatically.
 *   ?action=fixtures&league=ID  -> { success, fixtures: [...] }
 *                                   Upcoming fixtures for one league
 *                                   (league = ESPN path, e.g. "soccer/eng.2").
 *
 * Every response is cached in the sports_fixtures_cache table for
 * cache_ttl_seconds (default 300s) to keep ESPN call volume low.
 */

require_once __DIR__ . '/../includes/functions.php';

header('Content-Type: application/json; charset=UTF-8');

logRequest('api/sports-fixtures.php:' . (($_GET['action'] ?? '') ?: 'unknown'));

// ESPN mobile endpoint + iPhone UA (the desktop endpoint is Cloudflare-blocked).
const ESPN_UA = 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1';

// Curated active leagues: [sport, espn_path, display_name_override]
// UK first, then Europe, then international + USA. Every league is listed
// even when it has no fixtures today, so tomorrow's events appear on their own.
// Only paths confirmed to return valid ESPN JSON are included.
const ESPN_LEAGUES = [
    // ===== UK FOOTBALL =====
    ['Soccer', 'soccer/eng.1', 'English Premier League'],
    ['Soccer', 'soccer/eng.2', 'English Championship'],
    ['Soccer', 'soccer/eng.3', 'English League One'],
    ['Soccer', 'soccer/eng.4', 'English League Two'],
    ['Soccer', 'soccer/eng.fa', 'FA Cup'],
    ['Soccer', 'soccer/eng.league_cup', 'EFL Cup'],
    ['Soccer', 'soccer/eng.trophy', 'EFL Trophy'],
    ['Soccer', 'soccer/sco.1', 'Scottish Premiership'],
    ['Soccer', 'soccer/sco.2', 'Scottish Championship'],
    ['Soccer', 'soccer/sco.challenge', 'Scottish Challenge Cup'],
    ['Soccer', 'soccer/irl.1', 'League of Ireland'],

    // ===== EUROPEAN FOOTBALL =====
    ['Soccer', 'soccer/ger.1', 'German Bundesliga'],
    ['Soccer', 'soccer/ger.2', 'German 2. Bundesliga'],
    ['Soccer', 'soccer/ita.1', 'Italian Serie A'],
    ['Soccer', 'soccer/ita.2', 'Italian Serie B'],
    ['Soccer', 'soccer/esp.1', 'Spanish La Liga'],
    ['Soccer', 'soccer/esp.2', 'Spanish La Liga 2'],
    ['Soccer', 'soccer/fra.1', 'French Ligue 1'],
    ['Soccer', 'soccer/fra.2', 'French Ligue 2'],
    ['Soccer', 'soccer/ned.1', 'Dutch Eredivisie'],
    ['Soccer', 'soccer/por.1', 'Portuguese Primeira Liga'],
    ['Soccer', 'soccer/bel.1', 'Belgian Pro League'],
    ['Soccer', 'soccer/tur.1', 'Turkish Super Lig'],
    ['Soccer', 'soccer/gre.1', 'Greek Super League'],
    ['Soccer', 'soccer/den.1', 'Danish Superliga'],
    ['Soccer', 'soccer/aut.1', 'Austrian Bundesliga'],
    ['Soccer', 'soccer/sui.1', 'Swiss Super League'],
    ['Soccer', 'soccer/nor.1', 'Norwegian Eliteserien'],
    ['Soccer', 'soccer/swe.1', 'Swedish Allsvenskan'],
    ['Soccer', 'soccer/rus.1', 'Russian Premier League'],
    ['Soccer', 'soccer/uefa.champions', 'UEFA Champions League'],
    ['Soccer', 'soccer/uefa.europa', 'UEFA Europa League'],
    ['Soccer', 'soccer/uefa.europa.conf', 'UEFA Conference League'],
    ['Soccer', 'soccer/uefa.nations', 'UEFA Nations League'],
    ['Soccer', 'soccer/conmebol.libertadores', 'Copa Libertadores'],
    ['Soccer', 'soccer/conmebol.sudamericana', 'Copa Sudamericana'],
    ['Soccer', 'soccer/concacaf.league', 'CONCACAF League'],
    ['Soccer', 'soccer/fifa.friendly', 'International Friendlies'],
    ['Soccer', 'soccer/club.friendly', 'Club Friendlies'],

    // ===== INTERNATIONAL FOOTBALL =====
    ['Soccer', 'soccer/arg.1', 'Argentine Primera'],
    ['Soccer', 'soccer/bra.1', 'Brazilian Serie A'],
    ['Soccer', 'soccer/mex.1', 'Mexican Liga MX'],
    ['Soccer', 'soccer/mex.2', 'Mexican Liga de Expansión'],
    ['Soccer', 'soccer/usa.1', 'MLS'],
    ['Soccer', 'soccer/usa.nwsl', 'NWSL'],
    ['Soccer', 'soccer/chi.1', 'Chinese Super League'],
    ['Soccer', 'soccer/col.1', 'Colombian Primera A'],
    ['Soccer', 'soccer/uru.1', 'Uruguayan Primera'],
    ['Soccer', 'soccer/jpn.1', 'J1 League'],
    ['Soccer', 'soccer/aus.1', 'A-League'],
    ['Soccer', 'soccer/ksa.1', 'Saudi Pro League'],

    // ===== MOTORSPORT =====
    ['Motorsport', 'racing/f1', 'Formula 1'],
    ['Motorsport', 'racing/nascar-premier', 'NASCAR Cup Series'],
    ['Motorsport', 'racing/nascar-truck', 'NASCAR Truck'],
    ['Motorsport', 'racing/irl', 'IndyCar Series'],
    ['Motorsport', 'racing/nhra', 'NHRA Drag Racing'],

    // ===== TENNIS =====
    ['Tennis', 'tennis/atp', 'ATP World Tour'],
    ['Tennis', 'tennis/wta', 'WTA Tour'],

    // ===== GOLF =====
    ['Golf', 'golf/pga', 'PGA Tour'],
    ['Golf', 'golf/lpga', 'LPGA'],
    ['Golf', 'golf/eur', 'DP World Tour'],

    // ===== FIGHTING =====
    ['Fighting', 'mma/ufc', 'UFC'],
    ['Fighting', 'mma/pfl', 'PFL'],
    ['Fighting', 'mma/bellator', 'Bellator'],

    // ===== BASEBALL =====
    ['Baseball', 'baseball/mlb', 'MLB'],

    // ===== BASKETBALL =====
    ['Basketball', 'basketball/nba', 'NBA'],
    ['Basketball', 'basketball/wnba', 'WNBA'],

    // ===== AMERICAN FOOTBALL =====
    ['American Football', 'football/nfl', 'NFL'],
    ['American Football', 'football/cfl', 'CFL'],
    ['American Football', 'football/college-football', 'NCAA Football'],

    // ===== ICE HOCKEY =====
    ['Ice Hockey', 'hockey/nhl', 'NHL'],
];

function fixtures_config(): array {
    global $db;
    $rows = $db->select('sports_fixtures_config', '*', 'id = :id', '', [':id' => 1]);
    if (empty($rows)) {
        return ['enabled' => false, 'ttl' => 300];
    }
    $c = $rows[0];
    return [
        'enabled' => ((int) ($c['enabled'] ?? 0)) === 1,
        'ttl' => (int) ($c['cache_ttl_seconds'] ?? 300),
    ];
}

function cache_get(string $key, int $ttl): ?string {
    global $db;
    $rows = $db->select('sports_fixtures_cache', '*', 'cache_key = :k', '', [':k' => $key]);
    if (empty($rows)) return null;
    $row = $rows[0];
    if (time() - (int) $row['fetched_at'] > $ttl) return null;
    return $row['json'];
}

function cache_put(string $key, string $json): void {
    global $db;
    $existing = $db->select('sports_fixtures_cache', 'id', 'cache_key = :k', '', [':k' => $key]);
    $now = time();
    if (!empty($existing)) {
        $db->update('sports_fixtures_cache', ['json' => $json, 'fetched_at' => $now], 'cache_key = :k', [':k' => $key]);
    } else {
        $db->insert('sports_fixtures_cache', ['cache_key' => $key, 'json' => $json, 'fetched_at' => $now]);
    }
}

/**
 * Robust HTTP JSON fetch: cURL -> file_get_contents -> raw socket.
 * Uses the iPhone UA + SSL verify off (works on shared hosting).
 */
function fetch_upstream(string $url): ?string {
    $raw = false;

    // 1) cURL
    if (function_exists('curl_init')) {
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_TIMEOUT        => 8,
            CURLOPT_CONNECTTIMEOUT => 6,
            CURLOPT_USERAGENT      => ESPN_UA,
            CURLOPT_HTTPHEADER     => ['Accept: application/json'],
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => 0,
        ]);
        $raw = curl_exec($ch);
        $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        if ($raw === false || $raw === '' || $code < 200 || $code >= 400) {
            $raw = false;
        }
    }

    // 2) file_get_contents
    if ($raw === false && ini_get('allow_url_fopen')) {
        $ctx = stream_context_create([
            'http' => [
                'timeout' => 8,
                'header'  => "User-Agent: " . ESPN_UA . "\r\nAccept: application/json\r\n",
                'ignore_errors' => true,
            ],
            'ssl' => ['verify_peer' => false, 'verify_peer_name' => false],
        ]);
        $raw = @file_get_contents($url, false, $ctx);
        if ($raw === false || $raw === '') {
            $raw = false;
        }
    }

    // 3) raw socket (fsockopen) — last resort
    if ($raw === false && function_exists('fsockopen')) {
        $parts = parse_url($url);
        $host = $parts['host'] ?? '';
        $port = $parts['port'] ?? (($parts['scheme'] ?? 'https') === 'https' ? 443 : 80);
        $path = ($parts['path'] ?? '/') . (isset($parts['query']) ? '?' . $parts['query'] : '');
        $fp = @fsockopen(($parts['scheme'] ?? 'https') === 'https' ? 'ssl://' . $host : $host, $port, $errno, $errstr, 8);
        if ($fp) {
            $req = "GET $path HTTP/1.1\r\nHost: $host\r\nUser-Agent: " . ESPN_UA . "\r\nAccept: application/json\r\nConnection: close\r\n\r\n";
            fwrite($fp, $req);
            $resp = '';
            while (!feof($fp)) { $resp .= fgets($fp, 4096); }
            fclose($fp);
            $parts2 = explode("\r\n\r\n", $resp, 2);
            $head = $parts2[0] ?? '';
            $body = $parts2[1] ?? '';
            $status = 0;
            if (preg_match('#\s(\d{3})\s#', $head, $m)) { $status = (int) $m[1]; }
            if ($body !== '' && $status >= 200 && $status < 400) {
                if (stripos($head, 'Transfer-Encoding: chunked') !== false) {
                    $decoded = '';
                    $pos = 0;
                    while ($pos < strlen($body)) {
                        $eol = strpos($body, "\r\n", $pos);
                        if ($eol === false) { break; }
                        $size = hexdec(substr($body, $pos, $eol - $pos));
                        if ($size === 0) { break; }
                        $decoded .= substr($body, $eol + 2, $size);
                        $pos = $eol + 2 + $size + 2;
                    }
                    $body = $decoded;
                }
                $raw = $body;
            }
        }
    }

    if ($raw === false || $raw === '') {
        return null;
    }
    return $raw;
}

/** Cache-first fetch of an upstream JSON endpoint. Returns decoded array or null. */
function cached_fetch(string $cacheKey, string $upstreamUrl, int $ttl): ?array {
    $cached = cache_get($cacheKey, $ttl);
    if ($cached !== null) {
        $decoded = json_decode($cached, true);
        if (is_array($decoded)) return $decoded;
    }
    $body = fetch_upstream($upstreamUrl);
    if ($body === null) return null;
    $decoded = json_decode($body, true);
    if (!is_array($decoded)) return null;
    cache_put($cacheKey, $body);
    return $decoded;
}

/** 7-day date range (YYYYMMDD-YYYYMMDD) for the ESPN scoreboard. */
function espn_date_range(): string {
    $beg = gmdate('Ymd');
    $end = gmdate('Ymd', strtotime('+6 day') ?: time());
    return $beg . '-' . $end;
}

/** Fetch one league's scoreboard from ESPN. Returns decoded array or null. */
function espn_scoreboard(string $path, int $ttl): ?array {
    $url = 'https://site.web.api.espn.com/apis/site/v2/sports/' . $path . '/scoreboard?dates=' . espn_date_range();
    return cached_fetch('espn:' . $path, $url, $ttl);
}

/** Normalize an ESPN event into the app's fixture shape. */
function espn_to_fixture(array $e, string $leagueName): array {
    $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 ($home !== null) {
        $homeTeam = trim((string) ($home['team']['displayName'] ?? ''));
        $awayTeam = $away ? trim((string) ($away['team']['displayName'] ?? '')) : '';
        $event = $homeTeam . ' vs ' . $awayTeam;
    } else {
        $event = trim((string) ($e['name'] ?? $e['shortName'] ?? ''));
        $homeTeam = $event;
        $awayTeam = '';
    }

    return [
        'id' => (string) ($e['id'] ?? ''),
        'event' => $event,
        'league' => $leagueName,
        'date' => $date,
        'time' => $time,
        'home_team' => $homeTeam,
        'away_team' => $awayTeam,
    ];
}

$action = $_GET['action'] ?? '';
$cfg = fixtures_config();

if ($action === 'config') {
    // The app's SportsActivity loads a WebView from the "url" field.
    // Point it at the IBO-style 3-pane guide (webview.php?fetch=sport),
    // which auto-imports today's fixtures from ESPN (UK first).
    $scheme = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
    $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
    $base = rtrim(dirname(dirname($_SERVER['SCRIPT_NAME'])), '/\\');
    $url = $scheme . '://' . $host . $base . '/webview.php?fetch=sport';

    echo json_encode([
        'success' => true,
        'enabled' => $cfg['enabled'],
        'title' => 'Sports',
        'url' => $url,
        'javascript' => true,
        'allow_external' => false,
        'user_agent' => '',
        'maintenance_message' => '',
        'cache_mode' => 'default',
    ], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
    exit;
}

if (!$cfg['enabled']) {
    echo json_encode(['success' => false, 'error' => 'Sports fixtures disabled']);
    exit;
}

switch ($action) {
    case 'leagues': {
        // Return the full curated league list. id = ESPN path (the app
        // URL-encodes it and passes it back to ?action=fixtures&league=...).
        // "all" is a special entry at the top that returns every league's
        // fixtures merged into one list (UK first) — one tap, fully auto.
        $leagues = [
            ['id' => 'all', 'name' => 'All Today\'s Sports', 'sport' => 'All'],
        ];
        foreach (ESPN_LEAGUES as [$sport, $path, $name]) {
            $leagues[] = [
                'id' => $path,
                'name' => $name,
                'sport' => $sport,
            ];
        }
        echo json_encode(['success' => true, 'leagues' => $leagues], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
        break;
    }

    case 'fixtures': {
        $league = trim((string) ($_GET['league'] ?? ''));
        if ($league === '') {
            echo json_encode(['success' => false, 'error' => 'Missing league']);
            break;
        }

        // "all" = every league's fixtures merged into one list (UK first).
        if ($league === 'all') {
            $fixtures = [];
            foreach (ESPN_LEAGUES as [$sport, $path, $lname]) {
                $data = espn_scoreboard($path, $cfg['ttl']);
                if ($data === null) continue;
                foreach (($data['events'] ?? []) ?: [] as $e) {
                    $fixtures[] = espn_to_fixture($e, $lname);
                }
            }
            echo json_encode(['success' => true, 'fixtures' => $fixtures], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
            break;
        }

        // Find the display name for this path (fall back to the path itself).
        $name = $league;
        foreach (ESPN_LEAGUES as [$sport, $path, $lname]) {
            if ($path === $league) { $name = $lname; break; }
        }
        $data = espn_scoreboard($league, $cfg['ttl']);
        $fixtures = [];
        if ($data !== null) {
            foreach (($data['events'] ?? []) ?: [] as $e) {
                $fixtures[] = espn_to_fixture($e, $name);
            }
        }
        echo json_encode(['success' => true, 'fixtures' => $fixtures], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
        break;
    }

    default:
        echo json_encode(['success' => false, 'error' => 'Unknown action']);
}
