<?php
declare(strict_types=1);

if (!function_exists('station_bridge_default_server')) {
    function station_bridge_normalize_api_url(string $server): string
    {
        $server = trim($server);
        if ($server === '') {
            return '';
        }
        if (preg_match('/[\s,|]+/', $server)) {
            return '';
        }

        if (!preg_match('#^https?://#i', $server)) {
            $server = 'https://' . ltrim($server, '/');
        }

        $parsed = parse_url($server);
        $host = is_array($parsed) ? (string)($parsed['host'] ?? '') : '';
        if ($host === '') {
            return '';
        }

        $path = is_array($parsed) ? (string)($parsed['path'] ?? '') : '';
        $query = is_array($parsed) ? (string)($parsed['query'] ?? '') : '';
        $scheme = is_array($parsed) ? strtolower((string)($parsed['scheme'] ?? '')) : '';
        $port = is_array($parsed) && array_key_exists('port', $parsed) ? (int)$parsed['port'] : 0;

        if ($path === '' || $path === '/' || $path === '\\') {
            $server = ($scheme !== '' ? $scheme : 'https') . '://' . $host;
            if ($port > 0) {
                $server .= ':' . $port;
            }
            if ($query !== '') {
                $server .= '?' . $query;
            }
        }

        return preg_match('#^https?://#i', $server) ? $server : '';
    }

    function station_bridge_normalize_server(string $server): string
    {
        $server = trim($server);
        if ($server === '') {
            return '';
        }
        if (preg_match('/[\s,|]+/', $server)) {
            return '';
        }

        if (!preg_match('#^https?://#i', $server)) {
            $server = 'https://' . ltrim($server, '/');
        }

        $server = rtrim($server, '/');
        if (preg_match('#/public/api\.php$#i', $server)) {
            $server = (string)preg_replace('#/public/api\.php$#i', '', $server);
        } elseif (preg_match('#/api\.php$#i', $server)) {
            $server = (string)preg_replace('#/api\.php$#i', '', $server);
        } elseif (preg_match('#/public$#i', $server)) {
            $server = (string)preg_replace('#/public$#i', '', $server);
        }

        $host = parse_url($server, PHP_URL_HOST);
        if (!is_string($host) || trim($host) === '') {
            return '';
        }

        return rtrim($server, '/');
    }

    function station_bridge_configured_api_url(): string
    {
        return '';
    }

    function station_bridge_configured_api_url_candidates(): array
    {
        return [];
    }

    function station_bridge_embedded_server(): string
    {
        return '';
    }

    function station_bridge_guard_server(): string
    {
        return station_bridge_embedded_server();
    }

    function station_bridge_default_server(): string
    {
        return station_bridge_embedded_server();
    }
}

if (!function_exists('station_bridge_token')) {
    function station_bridge_token(): string
    {
        return '';
    }
}

if (!function_exists('station_bridge_api_url_retryable_error')) {
    function station_bridge_api_url_retryable_error(Throwable $e): bool
    {
        $message = strtolower(trim($e->getMessage()));
        if ($message === '') {
            return false;
        }
        if (str_contains($message, 'http 403') || str_contains($message, 'status 403') || str_contains($message, '403 forbidden')) {
            return false;
        }
        if (str_contains($message, 'http 301') || str_contains($message, 'http 302') || str_contains($message, 'http 307') || str_contains($message, 'http 308') || str_contains($message, 'http 404') || str_contains($message, 'http 405') || str_contains($message, 'http 408') || str_contains($message, 'http 500') || str_contains($message, 'http 502') || str_contains($message, 'http 503') || str_contains($message, 'http 504')) {
            return true;
        }
        if (str_contains($message, 'empty response') || str_contains($message, 'remote request failed') || str_contains($message, 'not valid json') || str_contains($message, 'stream_request_failed')) {
            return true;
        }
        return false;
    }
}

if (!function_exists('station_bridge_shared_secret')) {
    function station_bridge_shared_secret(): string
    {
        $secret = trim((string)(getenv('AUTH_STATION_BRIDGE_SECRET') ?: getenv('UPDATE_HMAC_SECRET') ?: ''));
        if ($secret === '' && function_exists('app_config')) {
            $cfg = app_config();
            $secret = trim((string)($cfg['update_hmac_secret'] ?? ''));
        }

        if ($secret === '' || strlen($secret) < 16) {
            return '';
        }

        return $secret;
    }
}

if (!function_exists('station_bridge_public_key')) {
    function station_bridge_public_key(): string
    {
        $raw = trim((string)(getenv('AUTH_STATION_BRIDGE_PUBLIC_KEY') ?: ''));
        if ($raw === '') {
            $file = trim((string)(getenv('AUTH_STATION_BRIDGE_PUBLIC_KEY_FILE') ?: ''));
            if ($file !== '' && is_file($file)) {
                $read = @file_get_contents($file);
                if (is_string($read)) {
                    $raw = trim($read);
                }
            }
        }

        if ($raw === '' && function_exists('app_config')) {
            $cfg = app_config();
            $raw = trim((string)($cfg['update_public_key'] ?? ''));
            if ($raw === '') {
                $cfgFile = trim((string)($cfg['update_public_key_file'] ?? ''));
                if ($cfgFile !== '' && is_file($cfgFile)) {
                    $read = @file_get_contents($cfgFile);
                    if (is_string($read)) {
                        $raw = trim($read);
                    }
                }
            }
        }

        if ($raw === '') {
            return '';
        }

        if (strpos($raw, '\n') !== false) {
            $raw = str_replace('\n', "\n", $raw);
        }

        return trim($raw);
    }
}

if (!function_exists('station_bridge_require_signature')) {
    function station_bridge_require_signature(): bool
    {
        return false;
    }
}

if (!function_exists('station_bridge_request_signature_headers')) {
    function station_bridge_request_signature_headers(string $domain): array
    {
        $secret = station_bridge_shared_secret();
        $domain = strtolower(trim($domain));
        if ($secret === '' || $domain === '') {
            return [];
        }

        try {
            $nonce = bin2hex(random_bytes(12));
        } catch (Throwable $e) {
            $nonce = bin2hex((string)microtime(true) . $domain);
            $nonce = substr($nonce, 0, 24);
        }
        $ts = (string)time();
        $base = $domain . "\n" . $ts . "\n" . $nonce;
        $sign = hash_hmac('sha256', $base, $secret);

        return [
            'X-Bridge-Ts: ' . $ts,
            'X-Bridge-Nonce: ' . $nonce,
            'X-Bridge-Sign: ' . $sign,
        ];
    }
}

if (!function_exists('station_bridge_request_signature_headers_for_request')) {
    function station_bridge_request_signature_headers_for_request(string $method, string $url, string $domain, string $body = ''): array
    {
        $secret = station_bridge_shared_secret();
        $domain = strtolower(trim($domain));
        if ($secret === '' || $domain === '') {
            return [];
        }

        try {
            $nonce = bin2hex(random_bytes(12));
        } catch (Throwable $e) {
            $nonce = substr(hash('sha256', microtime(true) . '|' . $domain), 0, 24);
        }
        $ts = (string)time();

        $parsed = parse_url($url);
        $path = (string)($parsed['path'] ?? '/');
        $query = [];
        if (!empty($parsed['query'])) {
            parse_str((string)$parsed['query'], $query);
            if (!is_array($query)) {
                $query = [];
            }
        }
        foreach (['token', 'api_token', 'api_key', 'update_token', 'auth_station_token', 'station_token', 'key'] as $sensitiveKey) {
            unset($query[$sensitiveKey]);
        }
        ksort($query);
        $queryJson = json_encode($query, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($queryJson)) {
            $queryJson = '{}';
        }

        $action = strtolower(trim((string)($query['action'] ?? 'check')));
        $bodyHash = hash('sha256', $body);
        $legacyBase = $domain . "\n" . $ts . "\n" . $nonce;
        $v2Base = strtoupper(trim($method)) . "\n"
            . $path . "\n"
            . $action . "\n"
            . $domain . "\n"
            . $ts . "\n"
            . $nonce . "\n"
            . $queryJson . "\n"
            . $bodyHash;

        return [
            'X-Bridge-Ts: ' . $ts,
            'X-Bridge-Nonce: ' . $nonce,
            'X-Bridge-Sign: ' . hash_hmac('sha256', $legacyBase, $secret),
            'X-Bridge-Sign-V2: ' . hash_hmac('sha256', $v2Base, $secret),
            'X-Bridge-Sign-Alg: hmac_sha256',
        ];
    }
}

if (!function_exists('station_bridge_sign_payload')) {
    function station_bridge_sign_payload(string $action, string $domain, string $ts, string $nonce, array $payload): string
    {
        $secret = station_bridge_shared_secret();
        if ($secret === '') {
            return '';
        }

        $action = strtolower(trim($action));
        $domain = strtolower(trim($domain));
        $json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($json)) {
            $json = '{}';
        }
        $base = $action . "\n" . $domain . "\n" . $ts . "\n" . $nonce . "\n" . $json;
        return hash_hmac('sha256', $base, $secret);
    }
}

if (!function_exists('station_bridge_verify_rsa_payload')) {
    function station_bridge_verify_rsa_payload(string $base, string $sign): bool
    {
        $publicKey = station_bridge_public_key();
        if ($publicKey === '' || !function_exists('openssl_verify')) {
            return false;
        }

        $sigBin = '';
        if ($sign !== '' && preg_match('/^[A-Fa-f0-9]+$/', $sign) && (strlen($sign) % 2 === 0)) {
            $hex = hex2bin($sign);
            if (is_string($hex)) {
                $sigBin = $hex;
            }
        }
        if ($sigBin === '') {
            $b64 = base64_decode($sign, true);
            if (is_string($b64)) {
                $sigBin = $b64;
            }
        }
        if ($sigBin === '') {
            return false;
        }

        $result = @openssl_verify($base, $sigBin, $publicKey, OPENSSL_ALGO_SHA256);
        return $result === 1;
    }
}

if (!function_exists('station_bridge_verify_signed_payload')) {
    function station_bridge_verify_signed_payload(array $json, string $action, string $domain): bool
    {
        $requireSignature = station_bridge_require_signature();
        $meta = $json['_bridge'] ?? null;
        if (!is_array($meta)) {
            return !$requireSignature;
        }

        $ts = trim((string)($meta['ts'] ?? ''));
        $nonce = trim((string)($meta['nonce'] ?? ''));
        $signCandidates = [];
        foreach (['sign', 'sign_legacy', 'sig'] as $key) {
            $value = trim((string)($meta[$key] ?? ''));
            if ($value !== '') {
                $signCandidates[] = $value;
            }
        }
        $signCandidates = array_values(array_unique($signCandidates));
        $alg = strtolower(trim((string)($meta['alg'] ?? 'hmac_sha256')));

        if ($ts === '' || !ctype_digit($ts) || $nonce === '' || !$signCandidates) {
            return !$requireSignature;
        }
        if (strlen($nonce) < 8 || strlen($nonce) > 96) {
            return !$requireSignature;
        }

        $now = time();
        $signedAt = (int)$ts;
        $windowSec = $requireSignature ? 3600 : 86400;
        if (abs($now - $signedAt) > $windowSec) {
            return !$requireSignature;
        }

        $payload = $json;
        unset($payload['_bridge']);
        $jsonPayload = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($jsonPayload)) {
            $jsonPayload = '{}';
        }

        $actionCandidates = [strtolower(trim($action))];
        $metaAction = strtolower(trim((string)($meta['action'] ?? '')));
        if ($metaAction !== '' && !in_array($metaAction, $actionCandidates, true)) {
            $actionCandidates[] = $metaAction;
        }
        foreach (['check', 'authorize', 'public_info'] as $fallbackAction) {
            if (!in_array($fallbackAction, $actionCandidates, true)) {
                $actionCandidates[] = $fallbackAction;
            }
        }

        $normalizeDomain = static function (string $value): string {
            $v = strtolower(trim($value));
            $v = preg_replace('/:\d+$/', '', $v);
            if (strpos($v, 'www.') === 0) {
                $v = substr($v, 4);
            }
            return trim((string)$v);
        };

        $domainCandidates = [];
        $domainCandidates[] = $normalizeDomain((string)$domain);
        $metaDomain = $normalizeDomain((string)($meta['domain'] ?? ''));
        if ($metaDomain !== '') {
            $domainCandidates[] = $metaDomain;
        }
        $serverHost = $normalizeDomain((string)($_SERVER['HTTP_HOST'] ?? ''));
        if ($serverHost !== '') {
            $domainCandidates[] = $serverHost;
        }
        $domainCandidates = array_values(array_unique(array_filter($domainCandidates, static function ($v): bool {
            return is_string($v) && $v !== '';
        })));

        if (!$domainCandidates) {
            return !$requireSignature;
        }

        $baseCandidates = [];
        foreach ($actionCandidates as $actionValue) {
            if ($actionValue === '') {
                continue;
            }
            foreach ($domainCandidates as $domainValue) {
                $baseCandidates[] = $actionValue . "\n"
                    . $domainValue . "\n"
                    . $ts . "\n"
                    . $nonce . "\n"
                    . $jsonPayload;
                $baseCandidates[] = $domainValue . "\n"
                    . $ts . "\n"
                    . $nonce . "\n"
                    . $jsonPayload;
            }
        }

        if (!$baseCandidates) {
            return !$requireSignature;
        }
        $baseCandidates = array_values(array_unique($baseCandidates));

        if (in_array($alg, ['rsa', 'rsa_sha256'], true)) {
            foreach ($baseCandidates as $base) {
                foreach ($signCandidates as $sign) {
                    if (station_bridge_verify_rsa_payload($base, $sign)) {
                        return true;
                    }
                }
            }
            if (!$requireSignature) {
                return false;
            }
        }

        $secret = station_bridge_shared_secret();
        if ($secret !== '') {
            foreach ($baseCandidates as $base) {
                $expected = hash_hmac('sha256', $base, $secret);
                foreach ($signCandidates as $sign) {
                    if (hash_equals(strtolower($expected), strtolower($sign))) {
                        return true;
                    }
                }
            }
            return false;
        }

        return !$requireSignature;
    }
}

if (!function_exists('station_bridge_security_config')) {
    function station_bridge_security_config(): array
    {
        if (function_exists('app_ensure_bridge_security_runtime')) {
            app_ensure_bridge_security_runtime();
        }
        if (function_exists('app_security_config')) {
            $cfg = app_security_config();
            return is_array($cfg) ? $cfg : [];
        }
        return [];
    }
}

if (!function_exists('station_bridge_host_allowed')) {
    function station_bridge_host_allowed(string $url): bool
    {
        $cfg = station_bridge_security_config();
        $allowed = $cfg['auth']['allowed_hosts'] ?? [];
        if (!is_array($allowed) || !$allowed) {
            return true;
        }

        $host = strtolower(trim((string)(parse_url($url, PHP_URL_HOST) ?: '')));
        if ($host === '') {
            return false;
        }

        $allowedHosts = array_map(static function ($item): string {
            return strtolower(trim((string)$item));
        }, $allowed);

        return in_array($host, $allowedHosts, true);
    }
}

if (!function_exists('station_bridge_security_mode')) {
    function station_bridge_security_mode(): string
    {
        $cfg = station_bridge_security_config();
        $mode = strtolower(trim((string)($cfg['auth']['mode'] ?? 'prefer_v2')));
        return in_array($mode, ['legacy', 'prefer_v2', 'strict_v2'], true) ? $mode : 'prefer_v2';
    }
}

if (!function_exists('station_bridge_security_public_key')) {
    function station_bridge_security_public_key(): string
    {
        $cfg = station_bridge_security_config();
        $key = trim((string)($cfg['auth']['public_key_pem'] ?? ''));
        if ($key === '') {
            $localAuthKey = dirname(__DIR__) . '/auth/storage/keys/bridge_public.pem';
            if (is_file($localAuthKey)) {
                $read = @file_get_contents($localAuthKey);
                if (is_string($read) && trim($read) !== '') {
                    $key = trim($read);
                }
            }
        }
        if ($key === '') {
            $key = station_bridge_public_key();
        }
        if ($key !== '' && strpos($key, '\n') !== false) {
            $key = str_replace('\n', "\n", $key);
        }
        return trim($key);
    }
}

if (!function_exists('station_bridge_security_issuer')) {
    function station_bridge_security_issuer(): string
    {
        $cfg = station_bridge_security_config();
        $issuer = trim((string)($cfg['auth']['issuer'] ?? ''));
        if ($issuer !== '') {
            return rtrim($issuer, '/');
        }

        $server = station_bridge_embedded_server();
        return rtrim($server, '/');
    }
}

if (!function_exists('station_bridge_security_audience')) {
    function station_bridge_security_audience(): string
    {
        $cfg = station_bridge_security_config();
        return trim((string)($cfg['auth']['audience'] ?? 'ly-verify-frontend'));
    }
}

if (!function_exists('station_bridge_install_id')) {
    function station_bridge_install_id(): string
    {
        $cfg = station_bridge_security_config();
        $id = trim((string)($cfg['local']['install_id'] ?? ''));
        if ($id !== '') {
            return $id;
        }

        return 'inst-missing';
    }
}

if (!function_exists('station_bridge_cache_key')) {
    function station_bridge_cache_key(): string
    {
        $cfg = station_bridge_security_config();
        $key = trim((string)($cfg['local']['cache_key'] ?? ''));
        if ($key !== '') {
            return $key;
        }

        if (function_exists('app_derived_secret')) {
            return app_derived_secret('bridge_cache');
        }

        return hash('sha256', __FILE__);
    }
}

if (!function_exists('station_bridge_machine_code')) {
    function station_bridge_machine_code(): string
    {
        $parts = [
            strtolower(trim((string)($_SERVER['SERVER_NAME'] ?? ''))),
            strtolower(trim((string)php_uname('n'))),
            strtolower(trim((string)php_uname('m'))),
            dirname(__DIR__),
        ];

        if (function_exists('db_config')) {
            $db = db_config();
            $parts[] = strtolower(trim((string)($db['host'] ?? '')));
            $parts[] = strtolower(trim((string)($db['database'] ?? '')));
        }

        return hash('sha256', implode('|', $parts));
    }
}

if (!function_exists('station_bridge_device_hash')) {
    function station_bridge_device_hash(): string
    {
        return hash('sha256', station_bridge_install_id() . '|' . station_bridge_machine_code());
    }
}

if (!function_exists('station_bridge_expected_domain')) {
    function station_bridge_expected_domain(): string
    {
        if (function_exists('station_current_domain')) {
            return (string)station_current_domain();
        }
        return strtolower(trim((string)($_SERVER['HTTP_HOST'] ?? '')));
    }
}

if (!function_exists('station_bridge_verify_envelope')) {
    function station_bridge_verify_signature_binary(string $message, string $signature, string $alg = ''): bool
    {
        $publicKey = station_bridge_security_public_key();
        if ($publicKey === '' || !function_exists('openssl_verify')) {
            return false;
        }

        $alg = strtoupper(trim($alg));
        if ($alg === 'RS256-PSS') {
            if (!defined('OPENSSL_PKCS1_PSS_PADDING')) {
                return false;
            }

            $verified = openssl_verify(
                $message,
                $signature,
                $publicKey,
                ['digest_alg' => 'sha256', 'rsa_padding_mode' => constant('OPENSSL_PKCS1_PSS_PADDING'), 'salt_length' => 32]
            );
            return $verified === 1;
        }

        if ($alg === 'RS256') {
            return openssl_verify($message, $signature, $publicKey, OPENSSL_ALGO_SHA256) === 1;
        }

        if (defined('OPENSSL_PKCS1_PSS_PADDING')) {
            $verified = openssl_verify(
                $message,
                $signature,
                $publicKey,
                ['digest_alg' => 'sha256', 'rsa_padding_mode' => constant('OPENSSL_PKCS1_PSS_PADDING'), 'salt_length' => 32]
            );
            if ($verified === 1) {
                return true;
            }
        }

        return openssl_verify($message, $signature, $publicKey, OPENSSL_ALGO_SHA256) === 1;
    }

    function station_bridge_verify_envelope(array $envelope, array $expected = []): array
    {
        $allowOfflineGrace = !empty($expected['_allow_offline_grace']);
        unset($expected['_allow_offline_grace']);

        foreach (['v', 'alg', 'kid', 'typ', 'payload', 'sig'] as $field) {
            if (!array_key_exists($field, $envelope)) {
                throw new RuntimeException('Envelope field missing: ' . $field);
            }
        }

        $publicKey = station_bridge_security_public_key();
        if ($publicKey === '' || !function_exists('openssl_verify')) {
            throw new RuntimeException('Bridge public key is not configured');
        }

        $sig = function_exists('app_b64url_decode')
            ? app_b64url_decode((string)$envelope['sig'])
            : base64_decode((string)$envelope['sig'], true);
        if (!is_string($sig) || $sig === '') {
            throw new RuntimeException('Envelope signature decode failed');
        }

        $unsigned = $envelope;
        unset($unsigned['sig']);
        $json = function_exists('app_canonical_json')
            ? app_canonical_json($unsigned)
            : json_encode($unsigned, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($json) || $json === '') {
            throw new RuntimeException('Envelope canonicalization failed');
        }

        if (!station_bridge_verify_signature_binary($json, $sig, (string)($envelope['alg'] ?? ''))) {
            throw new RuntimeException('Envelope signature invalid');
        }

        $payload = is_array($envelope['payload']) ? $envelope['payload'] : throw new RuntimeException('Envelope payload invalid');
        $now = time();
        if (isset($payload['nbf']) && (int)$payload['nbf'] > $now) {
            throw new RuntimeException('Envelope not yet valid');
        }
        if (isset($payload['exp']) && (int)$payload['exp'] < $now) {
            $offlineUntil = (int)($payload['offline_until'] ?? 0);
            $isTicket = hash_equals((string)$envelope['typ'], 'license_ticket');
            if (!$allowOfflineGrace || !$isTicket || $offlineUntil < $now) {
                throw new RuntimeException('Envelope expired');
            }
        }

        foreach ($expected as $key => $value) {
            $actual = $key === 'typ' ? (string)$envelope['typ'] : (string)($payload[$key] ?? '');
            if (!hash_equals((string)$value, $actual)) {
                throw new RuntimeException('Envelope claim mismatch: ' . $key);
            }
        }

        return $payload;
    }
}

if (!function_exists('station_bridge_http_post_json')) {
    function station_bridge_http_post_json(string $url, array $payload, array $headers = [], int $timeout = 15, int $connectTimeout = 5): array
    {
        $cfg = station_bridge_security_config();
        $timeout = max(6, (int)($cfg['update']['timeout'] ?? $timeout));
        $connectTimeout = max(3, min($timeout - 1, (int)($cfg['update']['connect_timeout'] ?? $connectTimeout)));
        $attempts = max(1, ((int)($cfg['update']['retries'] ?? 1)) + 1);

        $body = function_exists('app_canonical_json')
            ? app_canonical_json($payload)
            : json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
        if (!is_string($body)) {
            throw new RuntimeException('JSON encode failed');
        }

        if (function_exists('station_bridge_request_signature_headers_for_request')) {
            $headers = array_merge(
                $headers,
                station_bridge_request_signature_headers_for_request('POST', $url, station_bridge_expected_domain(), $body)
            );
        }

        $headers[] = 'Content-Type: application/json';
        $headers[] = 'Accept: application/json';

        $lastError = 'Remote request failed';
        for ($attempt = 0; $attempt < $attempts; $attempt++) {
            try {
                if (function_exists('curl_init')) {
                    $ch = curl_init();
                    curl_setopt_array($ch, [
                        CURLOPT_URL => $url,
                        CURLOPT_POST => true,
                        CURLOPT_POSTFIELDS => $body,
                        CURLOPT_HTTPHEADER => array_values(array_unique($headers)),
                        CURLOPT_RETURNTRANSFER => true,
                        CURLOPT_CONNECTTIMEOUT => $connectTimeout,
                        CURLOPT_TIMEOUT => $timeout,
                        CURLOPT_SSL_VERIFYPEER => true,
                        CURLOPT_SSL_VERIFYHOST => 2,
                        CURLOPT_NOSIGNAL => true,
                    ]);
                    $resp = curl_exec($ch);
                    $status = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
                    $error = curl_error($ch) ?: '';
                    curl_close($ch);
                    if (!is_string($resp) || $resp === '') {
                        throw new RuntimeException('Remote request failed: ' . ($error !== '' ? $error : 'empty response'));
                    }
                } else {
                    $context = stream_context_create([
                        'http' => [
                            'method' => 'POST',
                            'header' => implode("\r\n", array_values(array_unique($headers))),
                            'content' => $body,
                            'timeout' => $timeout,
                            'ignore_errors' => true,
                        ],
                    ]);
                    $resp = @file_get_contents($url, false, $context);
                    $status = 200;
                    if (isset($http_response_header) && is_array($http_response_header)) {
                        foreach ($http_response_header as $line) {
                            if (preg_match('#^HTTP/\S+\s+(\d{3})#', (string)$line, $m)) {
                                $status = (int)$m[1];
                                break;
                            }
                        }
                    }
                    if (!is_string($resp) || $resp === '') {
                        throw new RuntimeException('Remote request failed');
                    }
                }

                if ($status < 200 || $status >= 300) {
                    throw new RuntimeException('Remote request HTTP ' . $status);
                }

                $json = json_decode($resp, true);
                if (!is_array($json)) {
                    if (function_exists('app_log_api_event')) {
                        app_log_api_event('bridge_http_post_non_json', [
                            'url' => $url,
                            'status' => $status,
                        ]);
                    }
                    throw new RuntimeException('Remote response is not valid JSON');
                }

                if (function_exists('app_log_api_event')) {
                    app_log_api_event('bridge_http_post_ok', [
                        'url' => $url,
                        'status' => $status,
                    ]);
                }
                return $json;
            } catch (Throwable $e) {
                $lastError = $e->getMessage();
                if (function_exists('app_log_security_event')) {
                    app_log_security_event('bridge_http_post_failed', [
                        'url' => $url,
                        'attempt' => $attempt + 1,
                        'reason' => $lastError,
                    ]);
                }
                if ($attempt >= ($attempts - 1)) {
                    break;
                }
                usleep(200000 * ($attempt + 1));
            }
        }

        throw new RuntimeException($lastError);
    }
}

if (!function_exists('station_ticket_cache_name')) {
    function station_ticket_cache_name(string $suffix): string
    {
        return 'station_secure_' . $suffix . '_' . md5(station_bridge_expected_domain() . '|' . station_bridge_install_id());
    }
}

if (!function_exists('station_ticket_cache_write')) {
    function station_ticket_cache_write(string $suffix, array $envelope): void
    {
        if (!function_exists('station_secure_cache_set')) {
            return;
        }

        station_secure_cache_set(station_ticket_cache_name($suffix), [
            'ticket' => $envelope,
            'stored_at' => time(),
        ]);
    }
}

if (!function_exists('station_ticket_cache_raw')) {
    function station_ticket_cache_raw(string $suffix = 'site_ticket'): array
    {
        if (!function_exists('station_secure_cache_get')) {
            return [];
        }
        $cached = station_secure_cache_get(station_ticket_cache_name($suffix), 86400);
        $ticket = is_array($cached['ticket'] ?? null) ? (array)$cached['ticket'] : [];
        $payload = is_array($ticket['payload'] ?? null) ? (array)$ticket['payload'] : [];
        if (!$ticket || (int)($payload['exp'] ?? 0) < time()) {
            return [];
        }
        return $ticket;
    }
}

if (!function_exists('station_ticket_cache_read')) {
    function station_ticket_cache_read(string $suffix = 'site_ticket'): ?array
    {
        if (!function_exists('station_secure_cache_get')) {
            return null;
        }

        $cached = station_secure_cache_get(
            station_ticket_cache_name($suffix),
            (int)((station_bridge_security_config()['local']['offline_grace_seconds'] ?? 86400) + 300)
        );
        if (!is_array($cached) || !is_array($cached['ticket'] ?? null)) {
            return null;
        }

        $expected = [
            'typ' => 'license_ticket',
            'iss' => station_bridge_security_issuer(),
            'aud' => station_bridge_security_audience(),
            'domain' => station_bridge_expected_domain(),
            'install_id' => station_bridge_install_id(),
            'device_hash' => station_bridge_device_hash(),
            '_allow_offline_grace' => true,
        ];

        return station_bridge_verify_envelope((array)$cached['ticket'], $expected);
    }
}

if (!function_exists('station_manifest_cache_write')) {
    function station_manifest_cache_write(array $envelope): void
    {
        if (!function_exists('station_secure_cache_set')) {
            return;
        }

        station_secure_cache_set('station_secure_manifest_' . md5(station_bridge_expected_domain()), [
            'manifest' => $envelope,
            'stored_at' => time(),
        ]);
    }
}

if (!function_exists('station_manifest_cache_read')) {
    function station_manifest_cache_read(): ?array
    {
        if (!function_exists('station_secure_cache_get')) {
            return null;
        }

        $cached = station_secure_cache_get('station_secure_manifest_' . md5(station_bridge_expected_domain()), 3600);
        if (!is_array($cached) || !is_array($cached['manifest'] ?? null)) {
            return null;
        }

        return station_bridge_verify_envelope((array)$cached['manifest'], [
            'typ' => 'update_manifest',
            'iss' => station_bridge_security_issuer(),
            'aud' => station_bridge_security_audience(),
            'domain' => station_bridge_expected_domain(),
        ]);
    }
}

if (!function_exists('station_guard_authorize')) {
    function station_guard_authorize(bool $forceRefresh = false): array
    {
        $now = time();
        $domain = station_bridge_expected_domain();
        if ($domain === '') {
            $domain = trim((string)($_SERVER['HTTP_HOST'] ?? 'localhost'));
        }
        return [
            'license_id' => 'open-source',
            'domain' => $domain,
            'authorized' => true,
            'issued_at' => $now,
            'exp' => $now + 315360000,
            'offline_until' => $now + 315360000,
        ];
    }
}

if (!function_exists('station_fetch_update_manifest_secure')) {
    function station_fetch_update_manifest_secure(bool $forceRefresh = false): ?array
    {
        return null;
    }
}
