<?php
// Session 安全配置
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
    // 允许回调类脚本跳过 session（定义 SKIP_SESSION 常量即可）
    if (!defined('SKIP_SESSION') || !SKIP_SESSION) {
        ini_set('session.use_strict_mode', '1');
        ini_set('session.use_only_cookies', '1');
        ini_set('session.cookie_httponly', '1');
        ini_set('session.cookie_samesite', 'Lax');
        if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
            ini_set('session.cookie_secure', '1');
        }
        session_start();
    }

    // 安全响应头
    header('X-Frame-Options: SAMEORIGIN');
    header('X-Content-Type-Options: nosniff');
    header('X-XSS-Protection: 1; mode=block');
    header('Referrer-Policy: strict-origin-when-cross-origin');
}

define('APP_NAME', '蜜蜂AI建站');
define('APP_VERSION', '0.5.3');
define('APP_DEVELOPER', 'lex');

// 目录结构常量
define('BASE_PATH', dirname(__DIR__));
define('APP_PATH', __DIR__);
define('ADMIN_PATH', BASE_PATH . '/admin');
define('API_PATH', BASE_PATH . '/api');
define('CLI_PATH', BASE_PATH . '/cli');
define('AUTH_PATH', BASE_PATH . '/auth');

function is_cli_request()
{
    return PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg';
}

function app_runtime_config()
{
    if (array_key_exists('app_runtime_config_cache', $GLOBALS)) {
        return is_array($GLOBALS['app_runtime_config_cache']) ? $GLOBALS['app_runtime_config_cache'] : array();
    }
    $file = app_config_file_path();
    if (!file_exists($file)) {
        $GLOBALS['app_runtime_config_cache'] = array();
        return $GLOBALS['app_runtime_config_cache'];
    }
    // 使用 include 而非 require，避免 opcache 缓存导致读取到旧数据
    // 先尝试 opcache_invalidate 确保读取最新内容
    if (function_exists('opcache_invalidate')) {
        @opcache_invalidate($file, true);
    }
    $loaded = @include $file;
    $GLOBALS['app_runtime_config_cache'] = is_array($loaded) ? $loaded : array();
    return $GLOBALS['app_runtime_config_cache'];
}

function app_write_runtime_config($config)
{
    if (!is_array($config)) {
        throw new Exception('配置数据格式无效。');
    }
    $content = "<?php\nreturn " . var_export($config, true) . ";\n";
    $file = app_config_file_path();
    $result = file_put_contents($file, $content, LOCK_EX);
    if ($result === false) {
        throw new Exception('配置文件写入失败，请检查 app 目录写权限。');
    }
    // 写入后立即使 opcache 失效，防止后续请求读取到旧缓存
    if (function_exists('opcache_invalidate')) {
        @opcache_invalidate($file, true);
    }
    clearstatcache(true, $file);
    $GLOBALS['app_runtime_config_cache'] = $config;
    return true;
}

function app_update_runtime_config($values)
{
    $config = app_runtime_config();
    foreach ((array)$values as $key => $value) {
        $config[$key] = $value;
    }
    app_write_runtime_config($config);
    return $config;
}

function update_version_cache_file()
{
    return dirname(__DIR__) . '/runtime/version-check.json';
}

function update_check_cached()
{
    $file = update_version_cache_file();
    if (!file_exists($file)) {
        return null;
    }
    $data = @json_decode((string)file_get_contents($file), true);
    if (!is_array($data) || empty($data['version'])) {
        return null;
    }
    return $data;
}

function update_write_version_cache($data)
{
    $file = update_version_cache_file();
    $data['checked_at'] = time();
    $dir = dirname($file);
    if (!is_dir($dir)) { @mkdir($dir, 0755, true); }
    @file_put_contents($file, json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT), LOCK_EX);
    clearstatcache(true, $file);
    return $data;
}

function update_refresh_version_cache($ttlSeconds = 21600)
{
    $file = update_version_cache_file();
    clearstatcache(true, $file);
    if (file_exists($file)) {
        $age = time() - (int)filemtime($file);
        if ($age < $ttlSeconds) {
            return update_check_cached();
        }
    }
    try {
        if (function_exists('update_check_remote_version')) {
            $data = update_check_remote_version();
            return update_write_version_cache($data);
        }
        $url = defined('UPDATE_VERSION_URL') ? UPDATE_VERSION_URL : 'https://download.2090ai.cn/beeai/version.json';
        $ctx = stream_context_create(array(
            'http' => array('timeout' => 15, 'header' => "User-Agent: BeeAI/" . APP_VERSION . "\r\n"),
            'ssl' => array('verify_peer' => false, 'verify_peer_name' => false)
        ));
        $json = @file_get_contents($url, false, $ctx);
        if ($json === false) {
            return update_check_cached();
        }
        $data = json_decode($json, true);
        if (!is_array($data) || empty($data['version'])) {
            return update_check_cached();
        }
        return update_write_version_cache($data);
    } catch (Exception $e) {
        return update_check_cached();
    }
}

function update_has_cached_new_version()
{
    clearstatcache(true, update_version_cache_file());
    $cached = update_check_cached();
    if (!$cached || empty($cached['version'])) {
        return false;
    }
    return version_compare($cached['version'], APP_VERSION, '>');
}

function generate_task_runner_token()
{
    return bin2hex(random_bytes(24));
}

function request_remote_addr()
{
    if (!isset($_SERVER['REMOTE_ADDR'])) {
        return '';
    }
    return trim((string)$_SERVER['REMOTE_ADDR']);
}

function request_scheme()
{
    if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] !== '') {
        return strtolower(trim((string)$_SERVER['HTTP_X_FORWARDED_PROTO'])) === 'https' ? 'https' : 'http';
    }
    if (isset($_SERVER['REQUEST_SCHEME']) && $_SERVER['REQUEST_SCHEME'] !== '') {
        return strtolower(trim((string)$_SERVER['REQUEST_SCHEME'])) === 'https' ? 'https' : 'http';
    }
    if (!empty($_SERVER['HTTPS']) && strtolower((string)$_SERVER['HTTPS']) !== 'off') {
        return 'https';
    }
    if (isset($_SERVER['SERVER_PORT']) && (string)$_SERVER['SERVER_PORT'] === '443') {
        return 'https';
    }
    return 'http';
}

function request_host()
{
    if (isset($_SERVER['HTTP_HOST']) && trim((string)$_SERVER['HTTP_HOST']) !== '') {
        return trim((string)$_SERVER['HTTP_HOST']);
    }
    if (isset($_SERVER['SERVER_NAME']) && trim((string)$_SERVER['SERVER_NAME']) !== '') {
        return trim((string)$_SERVER['SERVER_NAME']);
    }
    return 'localhost';
}

function absolute_url($path)
{
    $path = '/' . ltrim((string)$path, '/');
    return request_scheme() . '://' . request_host() . $path;
}

function task_runner_absolute_url()
{
    return absolute_url('article-task-runner.php');
}

function is_local_request()
{
    $remoteAddr = request_remote_addr();
    return $remoteAddr === '127.0.0.1' || $remoteAddr === '::1';
}

function task_runner_token()
{
    $config = app_runtime_config();
    if (!isset($config['task_runner_token'])) {
        return '';
    }
    return trim((string)$config['task_runner_token']);
}

function request_task_runner_token()
{
    if (isset($_SERVER['HTTP_X_TASK_TOKEN'])) {
        return trim((string)$_SERVER['HTTP_X_TASK_TOKEN']);
    }
    if (isset($_POST['token'])) {
        return trim((string)$_POST['token']);
    }
    if (isset($_GET['token'])) {
        return trim((string)$_GET['token']);
    }
    return '';
}

function is_valid_task_runner_token_request()
{
    $expected = task_runner_token();
    if ($expected === '') {
        return false;
    }
    $provided = request_task_runner_token();
    if ($provided === '') {
        return false;
    }
    return hash_equals($expected, $provided);
}

function require_task_runner_access()
{
    if (is_cli_request() || is_local_request() || is_logged_in() || is_valid_task_runner_token_request()) {
        return;
    }
    http_response_code(403);
    exit('Forbidden');
}

function app_config_file_path()
{
    return __DIR__ . '/config.php';
}

function app_install_lock_file_path()
{
    return dirname(__DIR__) . '/runtime/install.lock';
}

function app_is_installed()
{
    return file_exists(app_config_file_path()) && file_exists(app_install_lock_file_path());
}

function e($value)
{
    return htmlspecialchars((string)$value, ENT_QUOTES, 'UTF-8');
}

function is_logged_in()
{
    return !empty($_SESSION['admin_logged_in']);
}

function require_login($checkLicense = true)
{
    if (!is_logged_in()) {
        header('Location: /login.php');
        exit;
    }
    if ($checkLicense) {
        require_license();
    }
}

function redirect($path)
{
    header('Location: ' . $path);
    exit;
}

// ==================== 多租户 ====================

function current_user_id()
{
    return isset($_SESSION['admin_id']) ? (int)$_SESSION['admin_id'] : 0;
}

function current_user_role()
{
    return isset($_SESSION['admin_role']) ? (string)$_SESSION['admin_role'] : 'user';
}

function is_super_admin()
{
    return current_user_role() === 'super_admin';
}

function admin_role_options()
{
    return array(
        'super_admin' => '超级管理员',
        'user' => '普通用户'
    );
}

function admin_role_label($role)
{
    $map = admin_role_options();
    return isset($map[$role]) ? $map[$role] : $role;
}

// ==================== CSRF 防护 ====================

function csrf_token()
{
    if (empty($_SESSION['_csrf_token'])) {
        $_SESSION['_csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['_csrf_token'];
}

function csrf_field()
{
    return '<input type="hidden" name="_csrf_token" value="' . e(csrf_token()) . '">';
}

function csrf_verify()
{
    $token = isset($_POST['_csrf_token']) ? trim((string)$_POST['_csrf_token']) : '';
    if ($token === '' || empty($_SESSION['_csrf_token'])) {
        return false;
    }
    return hash_equals($_SESSION['_csrf_token'], $token);
}

function require_csrf()
{
    if (!csrf_verify()) {
        http_response_code(403);
        exit('CSRF token 验证失败，请刷新页面重试。');
    }
}

// ==================== API Key 加密 ====================

function app_encryption_key()
{
    $config = app_runtime_config();
    if (isset($config['encryption_key']) && $config['encryption_key'] !== '') {
        return $config['encryption_key'];
    }
    $key = bin2hex(random_bytes(32));
    app_update_runtime_config(array('encryption_key' => $key));
    return $key;
}

function encrypt_value($plaintext)
{
    if ($plaintext === '') {
        return '';
    }
    $key = hex2bin(app_encryption_key());
    $iv = random_bytes(16);
    $encrypted = openssl_encrypt($plaintext, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
    if ($encrypted === false) {
        return $plaintext;
    }
    return 'enc:' . base64_encode($iv . $encrypted);
}

function decrypt_value($ciphertext)
{
    if ($ciphertext === '' || strpos($ciphertext, 'enc:') !== 0) {
        return $ciphertext;
    }
    $key = hex2bin(app_encryption_key());
    $raw = base64_decode(substr($ciphertext, 4));
    if ($raw === false || strlen($raw) < 17) {
        return '';
    }
    $iv = substr($raw, 0, 16);
    $encrypted = substr($raw, 16);
    $decrypted = openssl_decrypt($encrypted, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
    if ($decrypted === false) {
        return '';
    }
    return $decrypted;
}

function is_encrypted_value($value)
{
    return is_string($value) && strpos($value, 'enc:') === 0;
}

function decrypt_value_or_null($ciphertext)
{
    if ($ciphertext === '' || !is_encrypted_value($ciphertext)) {
        return $ciphertext;
    }
    $result = decrypt_value($ciphertext);
    return $result !== '' ? $result : null;
}

// ==================== 登录限流（基于文件 + IP） ====================

function login_throttle_dir()
{
    $dir = __DIR__ . '/../runtime/login_throttle';
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }
    return $dir;
}

function login_throttle_key($username)
{
    $ip = request_remote_addr();
    return md5(strtolower(trim($username)) . '|' . $ip);
}

function login_throttle_get($username)
{
    $file = login_throttle_dir() . '/' . login_throttle_key($username) . '.json';
    if (!file_exists($file)) {
        return array('count' => 0, 'locked_until' => 0);
    }
    $data = @json_decode((string)@file_get_contents($file), true);
    if (!is_array($data)) {
        return array('count' => 0, 'locked_until' => 0);
    }
    return array(
        'count' => isset($data['count']) ? (int)$data['count'] : 0,
        'locked_until' => isset($data['locked_until']) ? (int)$data['locked_until'] : 0,
    );
}

function login_throttle_set($username, $count, $lockedUntil)
{
    $file = login_throttle_dir() . '/' . login_throttle_key($username) . '.json';
    @file_put_contents($file, json_encode(array(
        'count' => (int)$count,
        'locked_until' => (int)$lockedUntil,
        'updated_at' => date('Y-m-d H:i:s'),
    )), LOCK_EX);
}

function login_throttle_clear($username)
{
    $file = login_throttle_dir() . '/' . login_throttle_key($username) . '.json';
    if (file_exists($file)) {
        @unlink($file);
    }
}

function login_throttle_cleanup($maxAgeSec = 3600)
{
    $dir = login_throttle_dir();
    $now = time();
    foreach (glob($dir . '/*.json') as $f) {
        if ($now - filemtime($f) > $maxAgeSec) {
            @unlink($f);
        }
    }
}

function require_license_guard($actionLabel = '当前操作', $allowManagePages = false, $requiredFeature = '')
{
    $scriptPath = isset($_SERVER['SCRIPT_NAME']) ? (string)$_SERVER['SCRIPT_NAME'] : '';
    $currentScript = basename($scriptPath);
    $currentTab = isset($_GET['tab']) ? trim((string)$_GET['tab']) : '';
    $isSettingsPage = (strpos($scriptPath, '/admin/settings/') !== false) || ($currentScript === 'settings.php');
    $isLicensePage = ($isSettingsPage && $currentTab === 'license');
    $isActivatePage = ($currentScript === 'activate.php');
    if ($allowManagePages && ($isLicensePage || $isActivatePage)) {
        return;
    }

    $lic = license_config();
    $hasCode = !empty($lic['activation_code']) && !empty($lic['license_key']);
    if (!$hasCode) {
        throw new Exception($actionLabel . '失败：系统尚未激活授权。');
    }

    if (license_is_expired()) {
        throw new Exception($actionLabel . '失败：授权已过期或已失效，请先前往授权管理重新验证。');
    }

    if ($requiredFeature !== '' && !license_has_feature($requiredFeature, $lic)) {
        throw new Exception($actionLabel . '失败：当前授权未开通该功能权限（' . $requiredFeature . '）。');
    }

    if (license_needs_verify()) {
        $result = license_verify_remote();
        if ($result['valid'] === false) {
            throw new Exception($actionLabel . '失败：' . (isset($result['msg']) ? $result['msg'] : '授权校验未通过。'));
        }
        if ($result['valid'] === null) {
            $lastVerify = isset($lic['last_verify_at']) ? (int)$lic['last_verify_at'] : 0;
            if ((time() - $lastVerify) > LICENSE_GRACE_PERIOD) {
                throw new Exception($actionLabel . '失败：授权服务器无法连接且已超过宽限期。');
            }
        }
        $lic = license_config();
        if ($requiredFeature !== '' && !license_has_feature($requiredFeature, $lic)) {
            throw new Exception($actionLabel . '失败：当前授权未开通该功能权限（' . $requiredFeature . '）。');
        }
    }
}

// ==================== 授权验证 ====================

define('LICENSE_API_BASE', 'https://beeai.suqianweb.cn');
define('LICENSE_API_SECRET', 'BeeAI2026SecretKey');
define('LICENSE_VERIFY_INTERVAL', 3600);
define('LICENSE_GRACE_PERIOD', 259200);
define('LICENSE_TICKET_PUBLIC_KEY_PATH', __DIR__ . '/../runtime/license_ticket_public.pem');

function license_config_key()
{
    $uid = current_user_id();
    return $uid > 0 ? 'license_user_' . $uid : 'license';
}

function license_config()
{
    $config = app_runtime_config();
    $key = license_config_key();

    // 1. 当前用户自己的 per-user key
    if (isset($config[$key]) && is_array($config[$key]) && !empty($config[$key])) {
        $own = $config[$key];
        // 如果自己的授权是 active 且未过期，直接返回
        if (_license_entry_is_active($own)) {
            return $own;
        }
    }

    // 2. 收集所有候选 license 来源（全局 + 各 license_user_*）
    $candidates = array();
    if (isset($config['license']) && is_array($config['license']) && !empty($config['license'])) {
        $candidates[] = $config['license'];
    }
    foreach ($config as $k => $v) {
        if (strpos($k, 'license_user_') === 0 && is_array($v) && !empty($v['activation_code'])) {
            $candidates[] = $v;
        }
    }

    // 3. 优先返回 active 且未过期的授权
    $fallback = null;
    foreach ($candidates as $c) {
        if (_license_entry_is_active($c)) {
            return $c;
        }
        if ($fallback === null && !empty($c['activation_code'])) {
            $fallback = $c;
        }
    }

    // 4. 没有 active 的，返回任意非空配置或空数组
    return $fallback !== null ? $fallback : array();
}

function _license_entry_is_active($lic)
{
    if (empty($lic['activation_code']) || empty($lic['license_key'])) {
        return false;
    }
    if (isset($lic['status']) && $lic['status'] !== 'active') {
        return false;
    }
    if (!empty($lic['end_date']) && strtotime($lic['end_date']) < strtotime(date('Y-m-d'))) {
        return false;
    }
    return true;
}

function license_has_signed_ticket($license = null)
{
    $lic = is_array($license) ? $license : license_config();
    return !empty($lic['ticket_payload']) && !empty($lic['ticket_signature']);
}

function license_ticket_public_key_pem()
{
    if (!is_file(LICENSE_TICKET_PUBLIC_KEY_PATH)) {
        return '';
    }
    $pem = @file_get_contents(LICENSE_TICKET_PUBLIC_KEY_PATH);
    return is_string($pem) ? trim($pem) : '';
}

function license_create_nonce()
{
    try {
        return bin2hex(random_bytes(16));
    } catch (Exception $e) {
        return sha1(uniqid((string)mt_rand(), true));
    }
}

function license_verify_signed_response($result)
{
    if (!is_array($result)) {
        return false;
    }
    $data = isset($result['data']) && is_array($result['data']) ? $result['data'] : null;
    if (!is_array($data) || empty($data['response_signature'])) {
        return true;
    }
    $signature = (string)$data['response_signature'];
    $algorithm = isset($data['response_algorithm']) ? strtoupper(trim((string)$data['response_algorithm'])) : 'HS256';
    $verifyData = $data;
    unset($verifyData['response_signature'], $verifyData['response_algorithm']);
    $payloadJson = json_encode($verifyData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    if (!is_string($payloadJson) || $payloadJson === '') {
        return false;
    }
    if ($algorithm === 'RS256') {
        $publicKeyPem = license_ticket_public_key_pem();
        if ($publicKeyPem === '' || !function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
            return false;
        }
        $publicKey = @openssl_pkey_get_public($publicKeyPem);
        if (!$publicKey) {
            return false;
        }
        $decodedSignature = base64_decode($signature, true);
        if ($decodedSignature === false || $decodedSignature === '') {
            return false;
        }
        $verified = @openssl_verify($payloadJson, $decodedSignature, $publicKey, OPENSSL_ALGO_SHA256);
        if (PHP_VERSION_ID >= 80000) {
            @openssl_pkey_free($publicKey);
        }
        return $verified === 1;
    }
    $expected = hash_hmac('sha256', $payloadJson, LICENSE_API_SECRET);
    return hash_equals($expected, $signature);
}

function license_verify_local_ticket($license = null)
{
    $lic = is_array($license) ? $license : license_config();
    if (!license_has_signed_ticket($lic)) {
        return array('valid' => null, 'reason' => 'missing_ticket', 'payload' => array());
    }
    $payloadEncoded = (string)$lic['ticket_payload'];
    $signature = (string)$lic['ticket_signature'];
    $payloadJson = base64_decode($payloadEncoded, true);
    if ($payloadJson === false || $payloadJson === '') {
        return array('valid' => false, 'reason' => 'invalid_payload', 'payload' => array());
    }
    $algorithm = isset($lic['ticket_algorithm']) ? strtoupper(trim((string)$lic['ticket_algorithm'])) : 'HS256';
    if ($algorithm === 'RS256') {
        $publicKeyPem = license_ticket_public_key_pem();
        if ($publicKeyPem === '' || !function_exists('openssl_pkey_get_public') || !function_exists('openssl_verify')) {
            return array('valid' => false, 'reason' => 'missing_public_key', 'payload' => array());
        }
        $publicKey = @openssl_pkey_get_public($publicKeyPem);
        if (!$publicKey) {
            return array('valid' => false, 'reason' => 'invalid_public_key', 'payload' => array());
        }
        $decodedSignature = base64_decode($signature, true);
        if ($decodedSignature === false || $decodedSignature === '') {
            return array('valid' => false, 'reason' => 'invalid_signature_encoding', 'payload' => array());
        }
        $verified = @openssl_verify($payloadJson, $decodedSignature, $publicKey, OPENSSL_ALGO_SHA256);
        if (PHP_VERSION_ID >= 80000) {
            @openssl_pkey_free($publicKey);
        }
        if ($verified !== 1) {
            return array('valid' => false, 'reason' => 'invalid_signature', 'payload' => array());
        }
    } else {
        $expected = hash_hmac('sha256', $payloadJson, LICENSE_API_SECRET);
        if (!hash_equals($expected, $signature)) {
            return array('valid' => false, 'reason' => 'invalid_signature', 'payload' => array());
        }
    }
    $payload = json_decode($payloadJson, true);
    if (!is_array($payload)) {
        return array('valid' => false, 'reason' => 'invalid_payload_json', 'payload' => array());
    }
    $expiresAt = isset($payload['ticket_expires_at']) ? (int)$payload['ticket_expires_at'] : 0;
    if ($expiresAt > 0 && $expiresAt < time()) {
        return array('valid' => false, 'reason' => 'ticket_expired', 'payload' => $payload);
    }
    if (!empty($payload['activation_code']) && !empty($lic['activation_code']) && (string)$payload['activation_code'] !== (string)$lic['activation_code']) {
        return array('valid' => false, 'reason' => 'activation_code_mismatch', 'payload' => $payload);
    }
    if (!empty($payload['license_key']) && !empty($lic['license_key']) && (string)$payload['license_key'] !== (string)$lic['license_key']) {
        return array('valid' => false, 'reason' => 'license_key_mismatch', 'payload' => $payload);
    }
    if (!empty($payload['domain']) && !empty($lic['domain']) && (string)$payload['domain'] !== (string)$lic['domain']) {
        return array('valid' => false, 'reason' => 'domain_mismatch', 'payload' => $payload);
    }
    return array('valid' => true, 'reason' => 'ok', 'payload' => $payload);
}

function license_ticket_is_valid($license = null)
{
    $result = license_verify_local_ticket($license);
    return $result['valid'] === true;
}

function license_features($license = null)
{
    $lic = is_array($license) ? $license : license_config();
    if (isset($lic['features']) && is_array($lic['features'])) {
        return array_values(array_unique(array_filter(array_map('strval', $lic['features']))));
    }
    $ticket = license_verify_local_ticket($lic);
    if ($ticket['valid'] === true && isset($ticket['payload']['features']) && is_array($ticket['payload']['features'])) {
        return array_values(array_unique(array_filter(array_map('strval', $ticket['payload']['features']))));
    }
    return [];
}

function license_has_feature($feature, $license = null)
{
    $feature = trim((string)$feature);
    if ($feature === '') {
        return true;
    }
    return in_array($feature, license_features($license), true);
}

function license_is_activated()
{
    $lic = license_config();
    if (empty($lic['activation_code']) || empty($lic['license_key'])) {
        return false;
    }
    if (isset($lic['status']) && in_array($lic['status'], array('invalid', 'expired'), true)) {
        return false;
    }
    if (!empty($lic['end_date']) && strtotime($lic['end_date']) < strtotime(date('Y-m-d'))) {
        return false;
    }
    if (license_has_signed_ticket($lic) && !license_ticket_is_valid($lic)) {
        return false;
    }
    return true;
}

function license_is_expired()
{
    $lic = license_config();
    if (empty($lic['activation_code'])) {
        return false;
    }
    if (isset($lic['status']) && in_array($lic['status'], array('invalid', 'expired'), true)) {
        if (is_cli_request() && !empty($lic['end_date']) && strtotime($lic['end_date']) >= strtotime(date('Y-m-d'))) {
            try {
                $refreshed = license_verify_remote();
                if (isset($refreshed['valid']) && $refreshed['valid'] === true) {
                    return false;
                }
            } catch (Exception $e) {}
            return false;
        }
        return true;
    }
    if (!empty($lic['end_date']) && strtotime($lic['end_date']) < strtotime(date('Y-m-d'))) {
        return true;
    }
    if (is_cli_request()) {
        return false;
    }
    $ticketCheck = license_verify_local_ticket($lic);
    if ($ticketCheck['valid'] === false) {
        $reason = isset($ticketCheck['reason']) ? $ticketCheck['reason'] : '';
        if ($reason !== 'ticket_expired') {
            return true;
        }
    }
    return false;
}

function license_normalize_status($status, $msg = '')
{
    $status = trim((string)$status);
    if (in_array($status, array('active', 'expired', 'disabled', 'invalid', 'inactive'), true)) {
        return $status;
    }
    $msg = trim((string)$msg);
    if ($msg === '') {
        return '';
    }
    if (mb_strpos($msg, '过期') !== false) {
        return 'expired';
    }
    if (mb_strpos($msg, '禁用') !== false) {
        return 'disabled';
    }
    if (mb_strpos($msg, '未激活') !== false) {
        return 'inactive';
    }
    if (mb_strpos($msg, '失效') !== false) {
        return 'invalid';
    }
    if (mb_strpos($msg, '不存在') !== false) {
        return 'invalid';
    }
    return '';
}

function license_days_remaining_value($endDate, $status = '')
{
    $status = trim((string)$status);
    if (in_array($status, array('expired', 'disabled', 'invalid', 'inactive'), true)) {
        return 0;
    }
    if (empty($endDate)) {
        return -1;
    }
    $days = (int)((strtotime($endDate) - strtotime(date('Y-m-d'))) / 86400);
    return max(0, $days);
}

function license_sync_local($changes, $baseLicense = null)
{
    $lic = is_array($baseLicense) ? $baseLicense : license_config();
    $merged = array_merge($lic, (array)$changes);
    $status = isset($merged['status']) ? license_normalize_status($merged['status']) : '';
    if ($status !== '') {
        $merged['status'] = $status;
    }
    $merged['days_remaining'] = license_days_remaining_value(
        isset($merged['end_date']) ? $merged['end_date'] : null,
        isset($merged['status']) ? $merged['status'] : ''
    );
    if (isset($merged['ticket_payload']) && $merged['ticket_payload'] === '') {
        unset($merged['ticket_payload']);
    }
    if (isset($merged['ticket_signature']) && $merged['ticket_signature'] === '') {
        unset($merged['ticket_signature']);
    }
    $licKey = license_config_key();
    app_update_runtime_config(array(
        $licKey => $merged
    ));
    return $merged;
}

function license_needs_verify()
{
    $lic = license_config();
    if (license_has_signed_ticket($lic) && !license_ticket_is_valid($lic)) {
        return true;
    }
    $lastVerify = isset($lic['last_verify_at']) ? (int)$lic['last_verify_at'] : 0;
    return (time() - $lastVerify) > LICENSE_VERIFY_INTERVAL;
}

function license_status_label()
{
    $lic = license_config();
    if (empty($lic['activation_code'])) {
        return '未激活';
    }
    if (!empty($lic['end_date']) && strtotime($lic['end_date']) < strtotime(date('Y-m-d'))) {
        return '已过期';
    }
    $status = isset($lic['status']) ? $lic['status'] : '';
    $map = array('active' => '已授权', 'expired' => '已过期', 'disabled' => '已禁用', 'invalid' => '授权失效');
    return isset($map[$status]) ? $map[$status] : '未知';
}

function license_days_remaining()
{
    $lic = license_config();
    return license_days_remaining_value(
        isset($lic['end_date']) ? $lic['end_date'] : null,
        isset($lic['status']) ? $lic['status'] : ''
    );
}

function license_end_date_display_text($license = null)
{
    $lic = is_array($license) ? $license : license_config();
    $endDate = isset($lic['end_date']) ? trim((string)$lic['end_date']) : '';
    $status = isset($lic['status']) ? trim((string)$lic['status']) : '';
    if ($endDate === '') {
        return '永久授权';
    }
    if (in_array($status, array('expired', 'disabled', 'invalid', 'inactive'), true)
        && strtotime($endDate) >= strtotime(date('Y-m-d'))) {
        return '已过期（以授权端校验结果为准）';
    }
    return $endDate;
}

function license_activate_remote($activationCode)
{
    $domain = request_host();
    $timestamp = time();
    $nonce = license_create_nonce();
    $username = isset($_SESSION['admin_username']) ? (string)$_SESSION['admin_username'] : '';
    $role = is_super_admin() ? 'super_admin' : 'user';
    $sign = md5($activationCode . $domain . $timestamp . $nonce . LICENSE_API_SECRET);

    $url = LICENSE_API_BASE . '/api/license/activate';
    $postData = json_encode(array(
        'activation_code' => $activationCode,
        'domain' => $domain,
        'username' => $username,
        'role' => $role,
        'timestamp' => $timestamp,
        'nonce' => $nonce,
        'sign' => $sign
    ));

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 15);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    $response = curl_exec($ch);
    $httpCode = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($response === false) {
        return array('success' => false, 'msg' => '无法连接授权服务器：' . $curlError);
    }

    $result = json_decode($response, true);
    if (!is_array($result)) {
        return array('success' => false, 'msg' => '授权服务器响应格式错误（HTTP ' . $httpCode . '）');
    }
    if (!license_verify_signed_response($result)) {
        return array('success' => false, 'msg' => '授权服务器响应签名校验失败');
    }

    if (isset($result['code']) && (int)$result['code'] === 1) {
        $data = isset($result['data']) && is_array($result['data']) ? $result['data'] : array();
        $ticketPayload = isset($data['ticket_payload']) ? (string)$data['ticket_payload'] : '';
        $ticketSignature = isset($data['ticket_signature']) ? (string)$data['ticket_signature'] : '';
        $ticketAlgorithm = isset($data['ticket_algorithm']) ? (string)$data['ticket_algorithm'] : 'HS256';
        if ($ticketPayload !== '' || $ticketSignature !== '') {
            $ticketCheck = license_verify_local_ticket(array(
                'activation_code' => $activationCode,
                'license_key' => isset($data['license_key']) ? $data['license_key'] : '',
                'domain' => isset($data['domain']) ? $data['domain'] : $domain,
                'ticket_payload' => $ticketPayload,
                'ticket_signature' => $ticketSignature,
                'ticket_algorithm' => $ticketAlgorithm,
            ));
            if ($ticketCheck['valid'] !== true) {
                return array('success' => false, 'msg' => '授权票据校验失败');
            }
        }
        license_sync_local(array(
            'activation_code' => $activationCode,
            'license_key' => isset($data['license_key']) ? $data['license_key'] : '',
            'domain' => $domain,
            'username' => $username,
            'licensee' => isset($data['licensee']) ? $data['licensee'] : '',
            'category' => isset($data['category']) ? $data['category'] : '',
            'status' => 'active',
            'start_date' => isset($data['start_date']) ? $data['start_date'] : date('Y-m-d'),
            'end_date' => isset($data['end_date']) ? $data['end_date'] : null,
            'days_remaining' => isset($data['days_remaining']) ? (int)$data['days_remaining'] : -1,
            'features' => isset($data['features']) && is_array($data['features']) ? $data['features'] : array(),
            'ticket_payload' => $ticketPayload,
            'ticket_signature' => $ticketSignature,
            'ticket_algorithm' => $ticketAlgorithm,
            'issued_at' => isset($data['issued_at']) ? (int)$data['issued_at'] : time(),
            'ticket_expires_at' => isset($data['ticket_expires_at']) ? (int)$data['ticket_expires_at'] : 0,
            'activated_at' => time(),
            'last_verify_at' => time()
        ));
        return array('success' => true, 'msg' => isset($result['msg']) ? $result['msg'] : '激活成功', 'data' => $data);
    }

    return array('success' => false, 'msg' => isset($result['msg']) ? $result['msg'] : '激活失败');
}

function license_verify_remote()
{
    $lic = license_config();
    if (empty($lic['activation_code']) || empty($lic['license_key'])) {
        return array('valid' => false, 'msg' => '未找到授权信息');
    }

    $code = $lic['activation_code'];
    $domain = (is_cli_request() && !empty($lic['domain'])) ? $lic['domain'] : request_host();
    $timestamp = time();
    $nonce = license_create_nonce();
    $sign = md5($code . $domain . $timestamp . $nonce . $lic['license_key']);

    $url = LICENSE_API_BASE . '/api/license/verify';
    $postData = json_encode(array(
        'activation_code' => $code,
        'domain' => $domain,
        'timestamp' => $timestamp,
        'nonce' => $nonce,
        'sign' => $sign
    ));

    $ch = curl_init($url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json'));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

    $response = curl_exec($ch);
    $curlError = curl_error($ch);
    curl_close($ch);

    if ($response === false) {
        return array('valid' => null, 'msg' => '无法连接授权服务器：' . $curlError);
    }

    $result = json_decode($response, true);
    if (!is_array($result)) {
        return array('valid' => null, 'msg' => '授权服务器响应格式错误');
    }
    if (!license_verify_signed_response($result)) {
        return array('valid' => false, 'msg' => '授权服务器响应签名校验失败', 'status' => 'invalid');
    }

    if (isset($result['code']) && (int)$result['code'] === 1) {
        $data = isset($result['data']) && is_array($result['data']) ? $result['data'] : array();
        $ticketPayload = isset($data['ticket_payload']) ? (string)$data['ticket_payload'] : '';
        $ticketSignature = isset($data['ticket_signature']) ? (string)$data['ticket_signature'] : '';
        $ticketAlgorithm = isset($data['ticket_algorithm']) ? (string)$data['ticket_algorithm'] : 'HS256';
        if ($ticketPayload !== '' || $ticketSignature !== '') {
            $ticketCheck = license_verify_local_ticket(array(
                'activation_code' => $code,
                'license_key' => isset($lic['license_key']) ? $lic['license_key'] : '',
                'domain' => $domain,
                'ticket_payload' => $ticketPayload,
                'ticket_signature' => $ticketSignature,
                'ticket_algorithm' => $ticketAlgorithm,
            ));
            if ($ticketCheck['valid'] !== true) {
                return array('valid' => false, 'msg' => '授权票据校验失败', 'data' => $data, 'status' => 'invalid');
            }
        }
        $status = license_normalize_status(isset($data['status']) ? $data['status'] : 'active', '授权有效');
        license_sync_local(array(
            'status' => $status !== '' ? $status : 'active',
            'licensee' => isset($data['licensee']) ? $data['licensee'] : (isset($lic['licensee']) ? $lic['licensee'] : ''),
            'category' => isset($data['category']) ? $data['category'] : (isset($lic['category']) ? $lic['category'] : ''),
            'start_date' => isset($data['start_date']) ? $data['start_date'] : (isset($lic['start_date']) ? $lic['start_date'] : null),
            'end_date' => array_key_exists('end_date', $data) ? $data['end_date'] : (isset($lic['end_date']) ? $lic['end_date'] : null),
            'features' => isset($data['features']) && is_array($data['features']) ? $data['features'] : (isset($lic['features']) && is_array($lic['features']) ? $lic['features'] : array()),
            'ticket_payload' => $ticketPayload,
            'ticket_signature' => $ticketSignature,
            'ticket_algorithm' => $ticketAlgorithm,
            'issued_at' => isset($data['issued_at']) ? (int)$data['issued_at'] : (isset($lic['issued_at']) ? (int)$lic['issued_at'] : 0),
            'ticket_expires_at' => isset($data['ticket_expires_at']) ? (int)$data['ticket_expires_at'] : (isset($lic['ticket_expires_at']) ? (int)$lic['ticket_expires_at'] : 0),
            'last_verify_at' => time()
        ), $lic);
        return array('valid' => true, 'msg' => '授权有效', 'data' => $data);
    }

    $failData = isset($result['data']) && is_array($result['data']) ? $result['data'] : array();
    $failMsg = isset($result['msg']) ? $result['msg'] : '授权验证失败';
    $failStatus = license_normalize_status(isset($failData['status']) ? $failData['status'] : '', $failMsg);
    if ($failStatus === '') {
        $failStatus = 'invalid';
    }
    $update = array(
        'status' => $failStatus,
        'last_verify_at' => time()
    );
    if (array_key_exists('end_date', $failData)) {
        $update['end_date'] = $failData['end_date'];
    }
    if (array_key_exists('start_date', $failData)) {
        $update['start_date'] = $failData['start_date'];
    }
    license_sync_local($update, $lic);
    return array('valid' => false, 'msg' => $failMsg, 'data' => $failData, 'status' => $failStatus);
}

function require_license()
{
    if (is_cli_request()) {
        return;
    }

    $scriptPath = isset($_SERVER['SCRIPT_NAME']) ? (string)$_SERVER['SCRIPT_NAME'] : '';
    $currentScript = basename($scriptPath);
    $currentTab = isset($_GET['tab']) ? trim((string)$_GET['tab']) : '';
    $isSettingsPage = (strpos($scriptPath, '/admin/settings/') !== false) || ($currentScript === 'settings.php');
    $isLicensePage = ($isSettingsPage && $currentTab === 'license');
    $isActivatePage = ($currentScript === 'activate.php');
    $isAllowedWhenLocked = $isLicensePage || $isActivatePage;

    $lic = license_config();
    $hasCode = !empty($lic['activation_code']) && !empty($lic['license_key']);

    // 普通用户无权访问授权管理页，锁定时只允许仪表盘（显示提示）
    if (!is_super_admin()) {
        if (!$hasCode || license_is_expired()) {
            $isDashboard = (strpos($scriptPath, '/admin/dashboard/') !== false) || ($currentScript === 'dashboard.php');
            if (!$isDashboard && !$isSettingsPage) {
                redirect('/admin/dashboard/index.php?license_error=1');
            }
        }
        return;
    }

    // 超管逻辑
    if (!$hasCode && !$isActivatePage) {
        redirect('/activate.php');
    }

    if (license_is_expired()) {
        if ($isSettingsPage && $currentTab !== 'license') {
            redirect('/admin/settings/index.php?tab=license&error=expired');
        }
        if (!$isAllowedWhenLocked) {
            redirect('/activate.php?error=expired');
        }
    }

    if (!$hasCode) {
        return;
    }

    if (license_needs_verify()) {
        $result = license_verify_remote();
        if ($result['valid'] === false) {
            $status = isset($result['status']) ? $result['status'] : '';
            if ($status === 'expired' || $status === 'disabled' || $status === 'invalid' || $status === 'inactive') {
                if ($isSettingsPage && $currentTab !== 'license') {
                    redirect('/admin/settings/index.php?tab=license&error=expired');
                }
                if (!$isAllowedWhenLocked) {
                    redirect('/activate.php?error=expired');
                }
            }
        }
        if ($result['valid'] === null) {
            $lastVerify = isset($lic['last_verify_at']) ? (int)$lic['last_verify_at'] : 0;
            if ((time() - $lastVerify) > LICENSE_GRACE_PERIOD) {
                if (!$isAllowedWhenLocked) {
                    redirect('/activate.php?error=unreachable');
                }
            }
        }
    }
}

// ── 插件引擎加载 ──
require_once __DIR__ . '/plugin-engine.php';
plugin_load_all();
