<?php
/**
 * 蜜蜂AI建站 · 在线升级模块
 *
 * 远程版本检查地址: https://download.2090ai.cn/beeai/version.json
 * 更新包格式:       https://download.2090ai.cn/beeai/beeai{version}.zip
 *
 * ZIP 包结构:
 *   beeai{version}.zip/
 *   ├── files/            → 直接覆盖到项目根目录
 *   ├── migrations/       → 数据库迁移 SQL（001.sql, 002.sql, ...）
 *   └── update.json       → 本次更新的元信息
 */

define('UPDATE_REMOTE_BASE', 'https://download.2090ai.cn/beeai/');
define('UPDATE_VERSION_URL', UPDATE_REMOTE_BASE . 'version.json');
define('UPDATE_TEMP_DIR', dirname(__DIR__) . '/runtime/update-temp');
define('UPDATE_BACKUP_DIR', dirname(__DIR__) . '/runtime/update-backup');
define('UPDATE_MIGRATIONS_DIR', dirname(__DIR__) . '/database/migrations');

/**
 * 从远端获取最新版本信息
 * 返回: array('version'=>'0.0.3', 'download_url'=>'...', 'changelog'=>'...', 'db_version'=>3, 'release_date'=>'...', 'min_php'=>'7.4')
 */
function update_check_remote_version()
{
    $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(UPDATE_VERSION_URL, false, $ctx);
    if ($json === false) {
        throw new Exception('无法连接更新服务器，请检查网络或稍后重试。');
    }
    // 去除 BOM 和首尾空白
    $json = ltrim($json, "\xEF\xBB\xBF");
    $json = trim($json);
    $data = json_decode($json, true);
    if (!is_array($data) || empty($data['version'])) {
        $errMsg = function_exists('json_last_error_msg') ? json_last_error_msg() : ('error code ' . json_last_error());
        throw new Exception('远程版本信息格式无效（JSON 解析: ' . $errMsg . '）。请检查 version.json 文件。');
    }
    return $data;
}

/**
 * 比较版本号，判断是否有更新
 */
function update_has_new_version($remoteVersion)
{
    return version_compare($remoteVersion, APP_VERSION, '>');
}

/**
 * 下载更新包到临时目录
 */
function update_download_package($downloadUrl)
{
    update_ensure_dir(UPDATE_TEMP_DIR);

    $zipPath = UPDATE_TEMP_DIR . '/update.zip';

    $ctx = stream_context_create(array(
        'http' => array(
            'timeout' => 120,
            'header' => "User-Agent: BeeAI/" . APP_VERSION . "\r\n"
        ),
        'ssl' => array(
            'verify_peer' => false,
            'verify_peer_name' => false
        )
    ));

    $content = @file_get_contents($downloadUrl, false, $ctx);
    if ($content === false || strlen($content) < 100) {
        throw new Exception('更新包下载失败，请检查网络连接。URL: ' . $downloadUrl);
    }

    if (file_put_contents($zipPath, $content) === false) {
        throw new Exception('更新包保存失败，请检查 runtime 目录写权限。');
    }

    return $zipPath;
}

/**
 * 解压更新包
 */
function update_extract_package($zipPath)
{
    if (!class_exists('ZipArchive')) {
        throw new Exception('服务器未安装 PHP ZipArchive 扩展，无法解压更新包。');
    }

    $extractDir = UPDATE_TEMP_DIR . '/extracted';
    update_ensure_dir($extractDir);

    $zip = new ZipArchive();
    $result = $zip->open($zipPath);
    if ($result !== true) {
        throw new Exception('更新包解压失败，错误码: ' . $result);
    }
    $zip->extractTo($extractDir);
    $zip->close();

    // 查找解压后的根目录（ZIP 内可能有一层目录包裹）
    $items = @scandir($extractDir);
    $dirs = array();
    if (is_array($items)) {
        foreach ($items as $item) {
            if ($item === '.' || $item === '..') continue;
            if (is_dir($extractDir . '/' . $item)) $dirs[] = $item;
        }
    }
    // 如果只有一个子目录且没有 files/ 目录，说明 ZIP 有一层包裹
    if (count($dirs) === 1 && !is_dir($extractDir . '/files')) {
        $extractDir = $extractDir . '/' . $dirs[0];
    }

    return $extractDir;
}

/**
 * 备份当前文件（只备份即将被覆盖的文件）
 */
function update_backup_files($extractDir)
{
    $filesDir = $extractDir . '/files';
    if (!is_dir($filesDir)) return array();

    $projectRoot = dirname(__DIR__);
    $backupDir = UPDATE_BACKUP_DIR . '/' . date('Ymd_His');
    update_ensure_dir($backupDir);

    $backed = array();
    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($filesDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $item) {
        $relativePath = substr($item->getPathname(), strlen($filesDir) + 1);
        $relativePath = str_replace('\\', '/', $relativePath);
        $targetPath = $projectRoot . '/' . $relativePath;

        if ($item->isDir()) {
            continue;
        }
        if (file_exists($targetPath)) {
            $backupPath = $backupDir . '/' . $relativePath;
            update_ensure_dir(dirname($backupPath));
            @copy($targetPath, $backupPath);
            $backed[] = $relativePath;
        }
    }

    return $backed;
}

/**
 * 覆盖项目文件
 */
function update_apply_files($extractDir, &$warnings = array())
{
    $filesDir = $extractDir . '/files';
    if (!is_dir($filesDir)) return 0;

    $projectRoot = dirname(__DIR__);
    $count = 0;

    $iterator = new RecursiveIteratorIterator(
        new RecursiveDirectoryIterator($filesDir, RecursiveDirectoryIterator::SKIP_DOTS),
        RecursiveIteratorIterator::SELF_FIRST
    );

    foreach ($iterator as $item) {
        $relativePath = substr($item->getPathname(), strlen($filesDir) + 1);
        $relativePath = str_replace('\\', '/', $relativePath);
        $targetPath = $projectRoot . '/' . $relativePath;

        if ($item->isDir()) {
            if (!is_dir($targetPath)) {
                @mkdir($targetPath, 0755, true);
            }
            continue;
        }

        @update_ensure_dir(dirname($targetPath));
        if (!@copy($item->getPathname(), $targetPath)) {
            $warnings[] = '跳过文件: ' . $relativePath . '（目录无写权限）';
            continue;
        }
        $count++;
    }

    return $count;
}

/**
 * 执行数据库迁移
 */
function update_run_migrations($extractDir)
{
    $migrationsDir = $extractDir . '/migrations';
    if (!is_dir($migrationsDir)) return 0;

    // 同时复制迁移文件到项目的 database/migrations/ 目录
    update_ensure_dir(UPDATE_MIGRATIONS_DIR);

    $files = @scandir($migrationsDir);
    if (!is_array($files)) return 0;

    $sqlFiles = array();
    foreach ($files as $f) {
        if (preg_match('/^(\d+).*\.sql$/i', $f, $m)) {
            $sqlFiles[(int)$m[1]] = $f;
        }
    }
    ksort($sqlFiles);

    if (empty($sqlFiles)) return 0;

    ensure_migrations_table();
    $pdo = db_connection();
    $executed = 0;

    foreach ($sqlFiles as $seq => $filename) {
        if (migration_already_applied($filename)) {
            continue;
        }

        $sql = file_get_contents($migrationsDir . '/' . $filename);
        if ($sql === false || trim($sql) === '') continue;

        // 复制到本地迁移目录
        @copy($migrationsDir . '/' . $filename, UPDATE_MIGRATIONS_DIR . '/' . $filename);

        try {
            // 支持多条 SQL 语句（用分号分隔）
            $statements = array_filter(array_map('trim', explode(';', $sql)));
            foreach ($statements as $stmt) {
                if ($stmt !== '') {
                    $pdo->exec($stmt);
                }
            }
            record_migration($filename);
            $executed++;
        } catch (Exception $e) {
            throw new Exception('数据库迁移失败 [' . $filename . ']: ' . $e->getMessage());
        }
    }

    return $executed;
}

/**
 * 清理临时文件
 */
function update_cleanup()
{
    if (is_dir(UPDATE_TEMP_DIR)) {
        update_rmdir_recursive(UPDATE_TEMP_DIR);
    }
}

/**
 * 从远程版本信息中提取需要逐步升级的版本路径
 * 如果远程提供 versions 数组，则筛选出 > 当前版本 的版本列表并按升序排列
 * 如果没有 versions 数组，则返回只含最新版本的单元素数组（兼容旧格式）
 */
function update_get_upgrade_path($remoteInfo)
{
    $currentVersion = APP_VERSION;
    $path = array();

    if (!empty($remoteInfo['versions']) && is_array($remoteInfo['versions'])) {
        foreach ($remoteInfo['versions'] as $v) {
            if (!isset($v['version']) || !isset($v['download_url'])) continue;
            if (version_compare($v['version'], $currentVersion, '>')) {
                $path[] = $v;
            }
        }
        usort($path, function ($a, $b) {
            return version_compare($a['version'], $b['version']);
        });
    }

    if (empty($path)) {
        $path[] = $remoteInfo;
    }

    return $path;
}

/**
 * 执行单个版本的升级步骤
 */
function update_apply_single_version($versionInfo, &$log, $topLevelInfo = array())
{
    $ver = isset($versionInfo['version']) ? $versionInfo['version'] : '?';

    if (!empty($versionInfo['min_php'])) {
        if (version_compare(PHP_VERSION, $versionInfo['min_php'], '<')) {
            throw new Exception('v' . $ver . ' 要求 PHP >= ' . $versionInfo['min_php'] . '，当前 ' . PHP_VERSION);
        }
    }

    $log[] = '正在下载 v' . $ver . ' 更新包...';
    $zipPath = update_download_package($versionInfo['download_url']);
    $log[] = '下载完成 (' . round(filesize($zipPath) / 1024) . ' KB)';

    $log[] = '正在解压更新包...';
    $extractDir = update_extract_package($zipPath);
    $log[] = '解压完成';

    $log[] = '正在备份当前文件...';
    $backed = update_backup_files($extractDir);
    $log[] = '已备份 ' . count($backed) . ' 个文件';

    $log[] = '正在更新文件...';
    $fileWarnings = array();
    $fileCount = update_apply_files($extractDir, $fileWarnings);
    $log[] = '已更新 ' . $fileCount . ' 个文件';
    foreach ($fileWarnings as $w) {
        $log[] = '⚠ ' . $w;
    }

    $log[] = '正在执行数据库迁移...';
    $migrationCount = update_run_migrations($extractDir);
    $log[] = '已执行 ' . $migrationCount . ' 个迁移';

    try {
        $recordInfo = $versionInfo;
        if (empty($recordInfo['changelog']) && !empty($topLevelInfo['changelog'])) {
            $recordInfo['changelog'] = $topLevelInfo['changelog'];
        }
        if (empty($recordInfo['release_date']) && !empty($topLevelInfo['release_date'])) {
            $recordInfo['release_date'] = $topLevelInfo['release_date'];
        }
        record_upgrade_history($recordInfo, $fileCount, $migrationCount);
    } catch (Exception $e) {}

    update_cleanup();

    return array('files' => $fileCount, 'migrations' => $migrationCount);
}

/**
 * 完整升级流程（一键升级，支持逐版本递进）
 *
 * 当远程 version.json 包含 versions 数组时，自动识别跨版本差距，
 * 逐步下载并应用每个中间版本的更新包，确保不遗漏文件和迁移。
 * 如果 version.json 为旧格式（无 versions 数组），则视为累积包直接升级。
 */
function update_perform_upgrade($remoteInfo)
{
    $log = array();
    $upgradePath = update_get_upgrade_path($remoteInfo);
    $totalSteps = count($upgradePath);

    if ($totalSteps > 1) {
        $log[] = '检测到跨版本升级：需要逐步升级 ' . $totalSteps . ' 个版本';
        $versions = array();
        foreach ($upgradePath as $v) {
            $versions[] = 'v' . $v['version'];
        }
        $log[] = '升级路径：v' . APP_VERSION . ' → ' . implode(' → ', $versions);
    } else {
        $log[] = '开始升级到 v' . $remoteInfo['version'];
    }

    $step = 0;
    foreach ($upgradePath as $versionInfo) {
        $step++;
        $ver = isset($versionInfo['version']) ? $versionInfo['version'] : '?';
        if ($totalSteps > 1) {
            $log[] = '';
            $log[] = '===== 步骤 ' . $step . '/' . $totalSteps . '：升级到 v' . $ver . ' =====';
        }

        $result = update_apply_single_version($versionInfo, $log, $remoteInfo);
        $log[] = 'v' . $ver . ' 升级完成（文件 ' . $result['files'] . ' 个，迁移 ' . $result['migrations'] . ' 个）';
    }

    update_cleanup_old_backups(5);
    $log[] = '临时文件已清理';

    $finalVersion = $upgradePath[$totalSteps - 1]['version'];
    $log[] = '全部升级完成！最终版本: v' . $finalVersion;
    return $log;
}

// ========== 数据库迁移跟踪 ==========

function ensure_migrations_table()
{
    static $checked = false;
    if ($checked) return;
    $checked = true;
    $pdo = db_connection();

    if (!table_exists('system_migrations')) {
        $pdo->exec("CREATE TABLE `system_migrations` (
            `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
            `migration` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '迁移文件名',
            `applied_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
            PRIMARY KEY (`id`),
            UNIQUE KEY `uk_migration` (`migration`)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='数据库迁移记录'");
    }
}

function migration_already_applied($filename)
{
    $pdo = db_connection();
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM system_migrations WHERE migration = :m");
    $stmt->execute(array(':m' => $filename));
    return (int)$stmt->fetchColumn() > 0;
}

function record_migration($filename)
{
    $pdo = db_connection();
    $pdo->prepare("INSERT IGNORE INTO system_migrations (migration, applied_at) VALUES (:m, NOW())")
        ->execute(array(':m' => $filename));
}

function list_applied_migrations()
{
    ensure_migrations_table();
    $pdo = db_connection();
    return $pdo->query("SELECT * FROM system_migrations ORDER BY id ASC")->fetchAll(PDO::FETCH_ASSOC);
}

// ========== 升级记录 ==========

function ensure_upgrade_history_table()
{
    $pdo = db_connection();
    $pdo->exec("CREATE TABLE IF NOT EXISTS `system_upgrade_history` (
        `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
        `version` VARCHAR(20) NOT NULL,
        `from_version` VARCHAR(20) NOT NULL DEFAULT '',
        `changelog` TEXT,
        `release_date` VARCHAR(20) DEFAULT NULL,
        `files_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `migrations_count` INT UNSIGNED NOT NULL DEFAULT 0,
        `upgraded_at` DATETIME NOT NULL,
        PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
}

function record_upgrade_history($remoteInfo, $fileCount = 0, $migrationCount = 0)
{
    ensure_upgrade_history_table();
    $pdo = db_connection();
    $stmt = $pdo->prepare("INSERT INTO system_upgrade_history (version, from_version, changelog, release_date, files_count, migrations_count, upgraded_at) VALUES (:version, :from_version, :changelog, :release_date, :files_count, :migrations_count, NOW())");
    $stmt->execute(array(
        ':version' => isset($remoteInfo['version']) ? $remoteInfo['version'] : '',
        ':from_version' => APP_VERSION,
        ':changelog' => isset($remoteInfo['changelog']) ? $remoteInfo['changelog'] : '',
        ':release_date' => isset($remoteInfo['release_date']) ? $remoteInfo['release_date'] : '',
        ':files_count' => $fileCount,
        ':migrations_count' => $migrationCount,
    ));
}

function list_upgrade_history()
{
    ensure_upgrade_history_table();
    $pdo = db_connection();
    return $pdo->query("SELECT * FROM system_upgrade_history ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC);
}

// ========== 工具函数 ==========

function update_ensure_dir($dir)
{
    if (!is_dir($dir)) {
        if (!@mkdir($dir, 0755, true)) {
            throw new Exception('无法创建目录: ' . $dir);
        }
    }
}

function update_rmdir_recursive($dir)
{
    if (!is_dir($dir)) return;
    $items = @scandir($dir);
    if (!is_array($items)) return;
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        $path = $dir . DIRECTORY_SEPARATOR . $item;
        if (is_dir($path)) {
            update_rmdir_recursive($path);
        } else {
            @unlink($path);
        }
    }
    @rmdir($dir);
}

/**
 * 获取已有的备份列表
 */
function update_list_backups()
{
    $backups = array();
    if (!is_dir(UPDATE_BACKUP_DIR)) return $backups;
    $items = @scandir(UPDATE_BACKUP_DIR);
    if (!is_array($items)) return $backups;
    foreach ($items as $item) {
        if ($item === '.' || $item === '..') continue;
        if (is_dir(UPDATE_BACKUP_DIR . '/' . $item)) {
            $backups[] = $item;
        }
    }
    rsort($backups);
    return $backups;
}

/**
 * 清理旧备份（保留最近 N 个）
 */
function update_cleanup_old_backups($keep = 5)
{
    $backups = update_list_backups();
    if (count($backups) <= $keep) return;
    $toDelete = array_slice($backups, $keep);
    foreach ($toDelete as $dir) {
        update_rmdir_recursive(UPDATE_BACKUP_DIR . '/' . $dir);
    }
}
