<?php
/**
 * NADIPLAY · Stalker / MAC portal proxy (cPanel PHP edition)
 *
 * Browsers can't talk directly to a Stalker portal because of CORS + the
 * fact that Chrome refuses to set `User-Agent` from JS. So we expose four
 * endpoints under /api/stalker/* that the frontend calls from the admin
 * "Test Connection" button and from the live-TV / VOD playback pages.
 *
 *     POST /api/stalker/handshake         { portal_url, mac } → { token, profile }
 *     POST /api/stalker/all-channels      { portal_url, mac } → { channels: [...] }
 *     POST /api/stalker/vod-categories    { portal_url, mac } → { categories: [...] }
 *     POST /api/stalker/resolve-stream    { portal_url, mac, cmd } → { url }
 *
 * Implementation impersonates a MAG250 STB and runs the standard Stalker
 * handshake → get_profile chain. Tokens are cached for 5 min in APCu (or a
 * shared-memory file as a fallback) so rapid channel zapping doesn't pay
 * the full handshake every time. Resolved URLs are cached for 30 s.
 */

const NADI_STALKER_TOKEN_TTL = 300;
const NADI_STALKER_URL_TTL   = 30;

const NADI_STALKER_STB_USER_AGENT =
    'Mozilla/5.0 (QtEmbedded; U; Linux; C) AppleWebKit/533.3 (KHTML, like Gecko) ' .
    'MAG200 stbapp ver: 4 rev: 250 Safari/533.3';
const NADI_STALKER_STB_X_USER_AGENT = 'Model: MAG250; Link: WiFi';

const NADI_STALKER_PORTAL_PATHS = [
    'portal.php',
    'c/portal.php',
    'stalker_portal/server/load.php',
];

const NADI_STALKER_MAC_RE = '/^([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}$/';

// ── tiny key/value cache (APCu when available, file fallback) ─────────────
function nadi_stalker_cache_dir(): string {
    $d = sys_get_temp_dir() . '/nadi_stalker_cache';
    if (!is_dir($d)) { @mkdir($d, 0700, true); }
    return $d;
}
function nadi_stalker_cache_get(string $key) {
    if (function_exists('apcu_fetch')) {
        $ok = false;
        $v = apcu_fetch("nadi_stalker:$key", $ok);
        return $ok ? $v : null;
    }
    $f = nadi_stalker_cache_dir() . '/' . sha1($key);
    if (!is_file($f)) return null;
    $row = @unserialize((string)@file_get_contents($f));
    if (!is_array($row)) return null;
    if (($row['exp'] ?? 0) < time()) { @unlink($f); return null; }
    return $row['v'] ?? null;
}
function nadi_stalker_cache_put(string $key, $value, int $ttl): void {
    if (function_exists('apcu_store')) {
        apcu_store("nadi_stalker:$key", $value, $ttl);
        return;
    }
    $f = nadi_stalker_cache_dir() . '/' . sha1($key);
    @file_put_contents($f, serialize(['v' => $value, 'exp' => time() + $ttl]), LOCK_EX);
}

// ── helpers ───────────────────────────────────────────────────────────────
function nadi_stalker_normalise_portal(string $raw): string {
    $raw = trim($raw);
    if ($raw === '') json_error(400, 'portal_url is required');
    if (!preg_match('#^https?://#i', $raw)) $raw = 'http://' . $raw;
    $p = parse_url($raw);
    if (empty($p['host'])) json_error(400, 'Invalid portal_url');
    $path = $p['path'] ?? '';
    $path = preg_replace(
        '#/(portal\.php|c/portal\.php|stalker_portal/server/load\.php)/?$#',
        '',
        $path
    );
    $port = isset($p['port']) ? ':' . $p['port'] : '';
    return ($p['scheme'] . '://' . $p['host'] . $port . rtrim((string)$path, '/'));
}

function nadi_stalker_headers(string $mac, ?string $token = null): array {
    $h = [
        'User-Agent: '   . NADI_STALKER_STB_USER_AGENT,
        'X-User-Agent: ' . NADI_STALKER_STB_X_USER_AGENT,
        'Cookie: mac=' . $mac . '; stb_lang=en; timezone=Europe/London',
        'Accept: */*',
        'Connection: Close',
    ];
    if ($token) $h[] = 'Authorization: Bearer ' . $token;
    return $h;
}

/** GET against the portal, trying each known entry path until we get JSON. */
function nadi_stalker_portal_get(string $portal_root, array $params, string $mac, ?string $token = null): array {
    $last_status = 0;
    $last_body   = '';
    foreach (NADI_STALKER_PORTAL_PATHS as $path) {
        $url = $portal_root . '/' . $path . '?' . http_build_query($params);
        $ch = curl_init($url);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS      => 4,
            CURLOPT_CONNECTTIMEOUT => 10,
            CURLOPT_TIMEOUT        => 20,
            CURLOPT_HTTPHEADER     => nadi_stalker_headers($mac, $token),
            // Stalker portals very often have broken / self-signed certs.
            // We don't transmit credentials over the response so this is safe.
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false,
        ]);
        $body = curl_exec($ch);
        $err  = curl_error($ch);
        $last_status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($body === false) {
            $last_body = $err;
            continue;
        }
        $last_body = substr((string)$body, 0, 300);
        if ($last_status === 200 && trim((string)$body) !== '') {
            $j = json_decode((string)$body, true);
            if (is_array($j)) return $j;
        }
    }
    json_error(502, "Portal returned no valid JSON (status=$last_status, body=" . json_encode($last_body) . ')');
    return []; // unreachable
}

/** Run handshake → get_profile. Returns [token, profile_array]. */
function nadi_stalker_full_auth(string $portal_root, string $mac, bool $force = false): array {
    $cache_key = "tok:$portal_root|$mac";
    if (!$force) {
        $cached = nadi_stalker_cache_get($cache_key);
        if (is_string($cached) && $cached !== '') {
            return [$cached, []];
        }
    }
    $hs = nadi_stalker_portal_get(
        $portal_root,
        [
            'type'          => 'stb',
            'action'        => 'handshake',
            'token'         => '',
            'JsHttpRequest' => '1-xml',
        ],
        $mac
    );
    $token = (($hs['js'] ?? null)['token'] ?? null) ?: ($hs['token'] ?? null);
    if (!$token) json_error(401, 'Stalker portal refused handshake (no token returned)');

    $prof = nadi_stalker_portal_get(
        $portal_root,
        [
            'type'             => 'stb',
            'action'           => 'get_profile',
            'JsHttpRequest'    => '1-xml',
            'hd'               => '1',
            'ver'              => 'ImageDescription: 0.2.18-r14-pub-250; ImageDate: Fri Jan 15 15:20:44 EET 2016; PORTAL version: 5.5.0; API Version: JS API version: 343; STB API version: 146; Player Engine version: 0x566',
            'num_banks'        => '2',
            'sn'               => '062017A0006789',
            'stb_type'         => 'MAG250',
            'client_type'      => 'STB',
            'image_version'    => '218',
            'video_out'        => 'hdmi',
            'device_id'        => '',
            'device_id2'       => '',
            'signature'        => '',
            'auth_second_step' => '1',
            'hw_version'       => '1.7-BD-00',
            'not_valid_token'  => '0',
            'metrics'          => '{"mac":"' . $mac . '","sn":"062017A0006789","type":"STB","model":"MAG250","uid":"","random":""}',
            'hw_version_2'     => '9d04a84afda8a2bd6dadc6a06b01bdcd1d3bc1e5',
            'timestamp'        => '1500000000',
            'api_signature'    => '262',
            'prehash'          => '',
        ],
        $mac,
        $token
    );
    nadi_stalker_cache_put($cache_key, $token, NADI_STALKER_TOKEN_TTL);
    return [(string)$token, ($prof['js'] ?? [])];
}

/** Validate body, normalise mac+portal, return [portal_root, mac]. */
function nadi_stalker_parse_body(): array {
    $body = json_decode(file_get_contents('php://input') ?: '', true);
    if (!is_array($body)) $body = [];
    $portal = (string)($body['portal_url'] ?? '');
    $mac    = strtoupper(trim((string)($body['mac'] ?? '')));
    if ($mac === '' || !preg_match(NADI_STALKER_MAC_RE, $mac)) {
        json_error(400, 'MAC must be in 00:1A:79:XX:XX:XX format');
    }
    if ($portal === '') json_error(400, 'portal_url is required');
    return [nadi_stalker_normalise_portal($portal), $mac, $body];
}

// ── Routes ────────────────────────────────────────────────────────────────
function route_stalker_handshake(): void {
    [$root, $mac] = nadi_stalker_parse_body();
    [$token, $profile] = nadi_stalker_full_auth($root, $mac, true);
    send_json([
        'ok'          => true,
        'token'       => $token,
        'portal_root' => $root,
        'mac'         => $mac,
        'profile'     => [
            'id'                  => $profile['id']          ?? null,
            'mac'                 => $profile['mac']         ?? $mac,
            'name'                => $profile['fname']       ?? ($profile['name'] ?? null),
            'phone'               => $profile['phone']       ?? null,
            'tariff_plan'         => $profile['tariff_plan'] ?? null,
            'blocked'             => !empty($profile['blocked']),
            'expire_billing_date' => $profile['end_date']    ?? ($profile['expire_billing_date'] ?? null),
        ],
    ]);
}

function route_stalker_all_channels(): void {
    [$root, $mac] = nadi_stalker_parse_body();
    [$token]      = nadi_stalker_full_auth($root, $mac);

    $data = nadi_stalker_portal_get(
        $root,
        ['type' => 'itv', 'action' => 'get_all_channels', 'JsHttpRequest' => '1-xml'],
        $mac, $token
    );
    $raw = (($data['js'] ?? [])['data'] ?? []);
    if (!is_array($raw)) $raw = [];

    // Genres (best-effort)
    $gmap = [];
    try {
        $g = nadi_stalker_portal_get(
            $root,
            ['type' => 'itv', 'action' => 'get_genres', 'JsHttpRequest' => '1-xml'],
            $mac, $token
        );
        $genres = $g['js'] ?? [];
        if (is_array($genres)) {
            foreach ($genres as $x) {
                $gmap[(string)($x['id'] ?? '')] = (string)($x['title'] ?? ($x['name'] ?? 'Uncategorized'));
            }
        }
    } catch (Throwable $e) { /* ignore — genres are optional */ }

    $channels = [];
    foreach ($raw as $ch) {
        $tv_genre = (string)($ch['tv_genre_id'] ?? '');
        $channels[] = [
            'id'            => (string)($ch['id'] ?? ($ch['xmltv_id'] ?? '')),
            'name'          => $ch['name'] ?? '',
            'logo'          => $ch['logo'] ?? ($ch['logo_big'] ?? ''),
            'category'      => $gmap[$tv_genre] ?? 'Uncategorized',
            'categoryId'    => $tv_genre !== '' ? $tv_genre : '0',
            'cmd'           => $ch['cmd'] ?? '',
            'epgChannelId'  => $ch['xmltv_id'] ?? '',
            'tv_archive'    => (int)($ch['tv_archive'] ?? 0),
            'censored'      => (int)($ch['censored']   ?? 0),
        ];
    }
    send_json(['channels' => $channels, 'total' => count($channels)]);
}

function route_stalker_vod_categories(): void {
    [$root, $mac] = nadi_stalker_parse_body();
    [$token]      = nadi_stalker_full_auth($root, $mac);

    $data = nadi_stalker_portal_get(
        $root,
        ['type' => 'vod', 'action' => 'get_categories', 'JsHttpRequest' => '1-xml'],
        $mac, $token
    );
    $cats = $data['js'] ?? [];
    if (!is_array($cats)) $cats = [];
    $out = [];
    foreach ($cats as $c) {
        $out[] = [
            'id'       => (string)($c['id'] ?? ''),
            'name'     => $c['title'] ?? ($c['name'] ?? 'Uncategorized'),
            'censored' => (int)($c['censored'] ?? 0),
        ];
    }
    send_json(['categories' => $out, 'total' => count($out)]);
}

function route_stalker_resolve_stream(): void {
    [$root, $mac, $body] = nadi_stalker_parse_body();
    $cmd = (string)($body['cmd'] ?? '');
    if ($cmd === '') json_error(400, 'cmd is required');

    $cache_key = "url:$root|$mac|$cmd";
    $cached = nadi_stalker_cache_get($cache_key);
    if (is_string($cached) && $cached !== '') {
        send_json(['url' => $cached, 'cached' => true]);
    }

    [$token] = nadi_stalker_full_auth($root, $mac);
    $data = nadi_stalker_portal_get(
        $root,
        ['type' => 'itv', 'action' => 'create_link', 'cmd' => $cmd, 'JsHttpRequest' => '1-xml'],
        $mac, $token
    );
    $url = (string)(($data['js'] ?? [])['cmd'] ?? '');
    if (preg_match('/^\s*ffmpeg\s+(.+)$/', $url, $m)) {
        $url = $m[1];
    }
    $url = trim($url);
    if ($url === '') json_error(502, 'Portal did not return a playable URL');
    nadi_stalker_cache_put($cache_key, $url, NADI_STALKER_URL_TTL);
    send_json(['url' => $url, 'cached' => false]);
}
