<?php

namespace App\Apps\Versionmgr\Services;

use App\Apps\Versionmgr\Models\SystemVersion;
use App\Apps\Versionmgr\Models\UpgradePackage;
use App\Http\Responses\ApiResponse;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;

class UpgradeService
{
    protected BackupService $backupService;

    protected PackageService $packageService;

    protected VersionCheckService $versionCheckService;

    public function __construct(
        BackupService $backupService,
        PackageService $packageService,
        VersionCheckService $versionCheckService
    ) {
        $this->backupService = $backupService;
        $this->packageService = $packageService;
        $this->versionCheckService = $versionCheckService;
    }

    public function startUpgrade(string $targetVersion = ''): array
    {
        // 升级过程涉及备份、解压、文件复制等大内存操作，提高限制
        @ini_set('memory_limit', '512M');
        @set_time_limit(0);

        // 同步执行所有升级步骤耗时长，尽早释放 session 锁，
        // 让前端 /api/upgrade/status 轮询请求可以并行进入读取实时进度
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_write_close();
        }

        // 检查是否有活跃任务
        $task = $this->findLatestTask();
        $activeStatuses = ['pending', 'running', 'rolling_back'];
        if ($task && in_array($task['status'], $activeStatuses)) {
            return ApiResponse::error(60013, '当前有升级任务正在执行');
        }

        // 无活跃任务，清除残留锁和残留任务文件
        $this->forceClearUpgradeLock();
        $taskFile = storage_path('app_versionmgr/upgrade_task.json');
        if (File::exists($taskFile)) {
            File::delete($taskFile);
        }

        $lock = Cache::lock('system_upgrade', 3600);
        if (!$lock->get()) {
            $this->forceClearUpgradeLock();
            $lock = Cache::lock('system_upgrade', 3600);
            if (!$lock->get()) {
                return ApiResponse::error(60013, '获取升级锁失败，请稍后重试');
            }
        }

        try {
            $current = SystemVersion::getCurrentVersion();
            if (!$current) {
                $lock->release();
                return ApiResponse::error(60003, '无法获取当前版本信息');
            }

            if (empty($targetVersion)) {
                $checkResult = $this->versionCheckService->checkUpdate();
                $targetVersion = $checkResult['data']['latest_version'] ?? '';
                if (empty($targetVersion) || version_compare($targetVersion, $current->version, '<=')) {
                    $lock->release();
                    return ApiResponse::error(60004, '没有可用的升级版本');
                }
            }

            $upgradePath = $this->versionCheckService->calculateUpgradePath($current->version, $targetVersion);

            if (empty($upgradePath)) {
                $lock->release();
                return ApiResponse::error(60004, '无法计算升级路径');
            }

            $this->preparePackages($upgradePath);

            $taskId = 'upgrade_' . date('Ymd_His');
            $this->saveUpgradeTask([
                'task_id' => $taskId,
                'from_version' => $current->version,
                'target_version' => $targetVersion,
                'upgrade_path' => $upgradePath,
                'current_step_index' => 0,
                'completed_steps' => [],
                'remaining_steps' => array_column($upgradePath, 'to'),
                'status' => 'running',
                'error_message' => null,
            ]);

            // 直接同步执行所有升级步骤，不走队列
            $totalSteps = count($upgradePath);
            $failed = false;
            $errorMsg = null;

            for ($i = 0; $i < $totalSteps; $i++) {
                try {
                    $stepSuccess = $this->executeUpgradeStep($taskId, $i);
                    if (!$stepSuccess) {
                        $failed = true;
                        $failedTask = $this->getUpgradeTask($taskId);
                        $errorMsg = $failedTask['error_message'] ?? "步骤 {$i} 执行失败";
                        break;
                    }
                } catch (\Throwable $stepEx) {
                    $failed = true;
                    $errorMsg = $stepEx->getMessage();
                    $this->updateUpgradeTask($taskId, [
                        'status' => 'failed',
                        'error_message' => $errorMsg,
                    ]);
                    break;
                }
            }

            $this->forceClearUpgradeLock();

            if (!$failed) {
                $this->updateUpgradeTask($taskId, ['status' => 'success']);
                return ApiResponse::success([
                    'task_id' => $taskId,
                    'from_version' => $current->version,
                    'target_version' => $targetVersion,
                    'upgrade_path' => array_column($upgradePath, 'to'),
                    'current_step' => $upgradePath[0]['to'] ?? '',
                    'status' => 'success',
                    'error_message' => null,
                ], '升级成功');
            }

            return ApiResponse::success([
                'task_id' => $taskId,
                'from_version' => $current->version,
                'target_version' => $targetVersion,
                'upgrade_path' => array_column($upgradePath, 'to'),
                'current_step' => $upgradePath[0]['to'] ?? '',
                'status' => 'failed',
                'error_message' => $errorMsg,
            ], '升级失败');
        } catch (\Throwable $e) {
            $this->forceClearUpgradeLock();
            return ApiResponse::error(60001, '启动升级失败：' . $e->getMessage());
        }
    }

    /**
     * 手动升级：按升级包 ID 直接同步执行，不走队列
     * 所有异常直接捕获并返回具体错误
     */
    public function upgradeByPackage(int $packageId): array
    {
        // 升级过程涉及备份、解压、文件复制等大内存操作，提高限制
        @ini_set('memory_limit', '512M');
        @set_time_limit(0);

        // 同步升级耗时长，尽早释放 session 锁，让前端轮询可并行读取进度
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_write_close();
        }

        $package = UpgradePackage::find($packageId);
        if (!$package) {
            return ApiResponse::error(60001, '升级包不存在');
        }
        if ($package->status !== 1) {
            return ApiResponse::error(60001, '该升级包已禁用');
        }

        $current = SystemVersion::getCurrentVersion();

        // 服务端版本校验：禁止降级及重复升级到当前版本，防止前端被绕过
        if ($current) {
            $cmp = version_compare($package->to_version, $current->version);
            if ($cmp < 0) {
                return ApiResponse::error(60005, '目标版本低于当前系统版本，不支持降级');
            }
            if ($cmp === 0) {
                if ((string) $package->build === (string) $current->build) {
                    return ApiResponse::error(60005, '当前系统已是该版本，无需升级');
                }

                return ApiResponse::error(60005, '目标版本与当前系统版本一致，不支持跨构建升级');
            }
        }

        // 强制清除残留锁和任务文件
        $this->forceClearUpgradeLock();
        $taskFile = storage_path('app_versionmgr/upgrade_task.json');
        if (File::exists($taskFile)) {
            File::delete($taskFile);
        }

        $fromVersion = $current ? $current->version : $package->from_version;
        $taskId = 'manual_' . date('Ymd_His');
        $upgradePath = [['from' => $package->from_version, 'to' => $package->to_version]];

        // 创建任务记录
        $this->saveUpgradeTask([
            'task_id' => $taskId,
            'from_version' => $fromVersion,
            'target_version' => $package->to_version,
            'upgrade_path' => $upgradePath,
            'current_step_index' => 0,
            'completed_steps' => [],
            'remaining_steps' => [$package->to_version],
            'status' => 'running',
            'error_message' => null,
        ]);

        // 直接同步执行，不走队列
        try {
            $success = $this->executeUpgradeStep($taskId, 0);

            if ($success) {
                $this->updateUpgradeTask($taskId, ['status' => 'success']);
                $this->forceClearUpgradeLock();

                return ApiResponse::success([
                    'task_id' => $taskId,
                    'from_version' => $fromVersion,
                    'target_version' => $package->to_version,
                    'status' => 'success',
                    'error_message' => null,
                ], '升级成功');
            }

            // executeUpgradeStep 返回 false 说明已捕获异常并标记 failed，读取错误信息
            $finalTask = $this->getUpgradeTask($taskId);
            $errorMsg = $finalTask['error_message'] ?? '升级步骤执行失败';

            $this->forceClearUpgradeLock();

            return ApiResponse::success([
                'task_id' => $taskId,
                'from_version' => $fromVersion,
                'target_version' => $package->to_version,
                'status' => 'failed',
                'error_message' => $errorMsg,
            ], '升级失败');
        } catch (\Throwable $e) {
            Log::error('手动升级异常', [
                'task_id' => $taskId,
                'error' => $e->getMessage(),
                'trace' => $e->getTraceAsString(),
            ]);

            $this->updateUpgradeTask($taskId, [
                'status' => 'failed',
                'error_message' => $e->getMessage(),
            ]);
            $this->forceClearUpgradeLock();

            return ApiResponse::success([
                'task_id' => $taskId,
                'from_version' => $fromVersion,
                'target_version' => $package->to_version,
                'status' => 'failed',
                'error_message' => $e->getMessage(),
            ], '升级失败');
        }
    }

    public function executeUpgradeStep(string $taskId, int $stepIndex): bool
    {
        $task = $this->getUpgradeTask($taskId);
        if (!$task) {
            Log::error('executeUpgradeStep: 任务不存在', ['task_id' => $taskId]);
            return false;
        }

        $upgradePath = $task['upgrade_path'];
        if (!isset($upgradePath[$stepIndex])) {
            Log::error('executeUpgradeStep: 步骤索引越界', [
                'task_id' => $taskId,
                'step_index' => $stepIndex,
                'total_steps' => count($upgradePath),
            ]);
            return false;
        }

        $step = $upgradePath[$stepIndex];
        Log::info('开始执行升级步骤', [
            'task_id' => $taskId,
            'step' => $stepIndex,
            'from' => $step['from'],
            'to' => $step['to'],
        ]);

        $this->updateUpgradeTask($taskId, [
            'status' => 'running',
            'current_step_index' => $stepIndex,
        ]);

        try {
            // 1. 查找升级包
            $package = UpgradePackage::findByVersions($step['from'], $step['to']);
            if (!$package) {
                throw new \RuntimeException("升级包不存在：{$step['from']} -> {$step['to']}");
            }
            Log::info('升级包已定位', ['package_id' => $package->id, 'path' => $package->package_path]);

            // 2. 解压升级包
            $extracted = $this->packageService->extractPackage($package->package_path);
            if (!$extracted) {
                throw new \RuntimeException('升级包解压失败：' . $package->package_path);
            }
            Log::info('升级包已解压', ['extract_dir' => $extracted['extract_dir']]);

            $upgradeJson = $extracted['upgrade_json'];
            $extractDir = $extracted['extract_dir'];

            // 3. 校验版本号
            if ($upgradeJson['from_version'] !== $step['from'] || $upgradeJson['to_version'] !== $step['to']) {
                $this->packageService->cleanupExtract($extractDir);
                throw new \RuntimeException("升级包版本号不匹配：期望 {$step['from']}->{$step['to']}，实际 {$upgradeJson['from_version']}->{$upgradeJson['to_version']}");
            }

            // 4. 执行升级前脚本
            Log::info('执行升级前脚本');
            $this->executeBeforeScript($extractDir, $upgradeJson);

            // 5. 创建备份
            Log::info('创建备份');
            $backupPath = $this->backupService->createBackup($step['from'], $upgradeJson);
            if (!$backupPath) {
                $this->packageService->cleanupExtract($extractDir);
                throw new \RuntimeException('备份创建失败');
            }
            Log::info('备份完成', ['backup_path' => $backupPath]);

            // 6. 应用代码文件
            Log::info('应用代码文件');
            $this->applyCodeFiles($extractDir, $upgradeJson);

            // 7. 执行数据库迁移
            Log::info('执行数据库迁移');
            $this->runMigrations($extractDir);

            // 8. 删除文件
            Log::info('删除文件');
            $this->deleteFiles($upgradeJson);

            // 9. 执行升级后脚本
            Log::info('执行升级后脚本');
            $this->executeAfterScript($extractDir, $upgradeJson);

            // 10. 更新系统版本
            Log::info('更新系统版本记录');
            $this->updateSystemVersion($step['to'], $upgradeJson, $backupPath);

            Cache::forget('system_current_version');
            Cache::forget('system_current_build');
            Artisan::call('cache:clear');

            $this->packageService->cleanupExtract($extractDir);

            $completedSteps = $task['completed_steps'];
            $completedSteps[] = $step['to'];

            $remainingSteps = $task['remaining_steps'];
            $remainingSteps = array_values(array_diff($remainingSteps, [$step['to']]));

            $this->updateUpgradeTask($taskId, [
                'completed_steps' => $completedSteps,
                'remaining_steps' => $remainingSteps,
            ]);

            Log::info('升级步骤执行成功', ['task_id' => $taskId, 'step' => $stepIndex]);

            return true;
        } catch (\Throwable $e) {
            Log::error('升级步骤执行失败', [
                'task_id' => $taskId,
                'step' => $stepIndex,
                'error' => $e->getMessage(),
                'file' => $e->getFile(),
                'line' => $e->getLine(),
            ]);

            $this->updateUpgradeTask($taskId, [
                'status' => 'failed',
                'error_message' => $e->getMessage(),
            ]);

            try {
                $this->executeRollback($taskId, $stepIndex);
            } catch (\Throwable $rollbackEx) {
                Log::error('回滚过程异常', [
                    'task_id' => $taskId,
                    'error' => $rollbackEx->getMessage(),
                ]);
            }

            return false;
        }
    }

    protected function applyCodeFiles(string $extractDir, array $upgradeJson): void
    {
        $files = $upgradeJson['files'] ?? [];
        $addFiles = $files['add'] ?? [];
        $modifyFiles = $files['modify'] ?? [];
        $allCopyFiles = array_merge($addFiles, $modifyFiles);

        foreach ($allCopyFiles as $relativePath) {
            $sourcePath = $extractDir . '/files/' . $relativePath;
            $targetPath = base_path($relativePath);

            if (!File::exists($sourcePath)) {
                $sourcePath = $this->findFileInExtract($extractDir, $relativePath);
                if (!$sourcePath) {
                    Log::warning('升级包中文件不存在', ['file' => $relativePath]);
                    continue;
                }
            }

            $destDir = dirname($targetPath);
            if (!File::isDirectory($destDir)) {
                File::makeDirectory($destDir, 0755, true);
            }

            // 权限诊断：检查目标目录/文件是否可写
            if (!is_writable($destDir)) {
                $dirPerms = substr(sprintf('%o', fileperms($destDir)), -4);
                $dirOwner = function_exists('posix_getpwuid') ? posix_getpwuid(fileowner($destDir))['name'] ?? 'unknown' : 'unknown';
                $phpUser = function_exists('posix_geteuid') ? posix_getpwuid(posix_geteuid())['name'] ?? 'unknown' : 'unknown';
                throw new \RuntimeException(
                    "目录不可写：{$relativePath}\n" .
                    "目录：{$destDir}\n" .
                    "权限：{$dirPerms}，属主：{$dirOwner}，PHP用户：{$phpUser}\n" .
                    "请在服务器执行：chmod -R 755 " . base_path() . " && chown -R www:www " . base_path()
                );
            }

            if (File::exists($targetPath) && !is_writable($targetPath)) {
                $filePerms = substr(sprintf('%o', fileperms($targetPath)), -4);
                throw new \RuntimeException(
                    "文件不可写：{$relativePath}\n" .
                    "文件：{$targetPath}\n" .
                    "权限：{$filePerms}\n" .
                    "请在服务器执行：chmod -R 755 " . base_path() . " && chown -R www:www " . base_path()
                );
            }

            if (!File::copy($sourcePath, $targetPath)) {
                $error = error_get_last()['message'] ?? '未知错误';
                throw new \RuntimeException("文件替换失败：{$relativePath}（{$error}）");
            }

            Log::info('文件已复制', ['file' => $relativePath]);
        }
    }

    protected function findFileInExtract(string $extractDir, string $relativePath): ?string
    {
        $possiblePaths = [
            $extractDir . '/files/add/' . $relativePath,
            $extractDir . '/files/modify/' . $relativePath,
            $extractDir . '/' . $relativePath,
        ];

        foreach ($possiblePaths as $path) {
            if (File::exists($path)) {
                return $path;
            }
        }

        return null;
    }

    protected function runMigrations(string $extractDir): void
    {
        $migrationPath = $extractDir . '/migrations';
        if (!File::isDirectory($migrationPath)) {
            return;
        }

        $relativePath = str_replace(base_path() . '/', '', $migrationPath);

        $migrationFiles = File::glob($migrationPath . '/*.php');
        sort($migrationFiles);

        $pendingMigrations = [];
        foreach ($migrationFiles as $file) {
            $migrationName = pathinfo($file, PATHINFO_FILENAME);
            if (!DB::table('migrations')->where('migration', $migrationName)->exists()) {
                $pendingMigrations[] = $migrationName;
            }
        }

        if (empty($pendingMigrations)) {
            return;
        }

        Artisan::call('migrate', [
            '--path' => $relativePath,
            '--force' => true,
        ]);

        $output = Artisan::output();
        if (str_contains($output, 'ErrorException') || str_contains($output, 'SQLSTATE')) {
            throw new \RuntimeException('数据库迁移执行失败：' . $output);
        }
    }

    protected function deleteFiles(array $upgradeJson): void
    {
        $files = $upgradeJson['files'] ?? [];
        $deleteFiles = $files['delete'] ?? [];

        foreach ($deleteFiles as $relativePath) {
            $targetPath = base_path($relativePath);
            if (File::exists($targetPath)) {
                File::delete($targetPath);
            }
        }
    }

    protected function executeBeforeScript(string $extractDir, array $upgradeJson): void
    {
        if (empty($upgradeJson['has_before_script'])) {
            return;
        }

        $scriptPath = $extractDir . '/scripts/before_upgrade.php';
        if (!File::exists($scriptPath)) {
            return;
        }

        try {
            // 用输出缓冲吞掉脚本内的 echo 输出，避免污染接口 JSON 响应
            ob_start();
            try {
                $closure = require $scriptPath;
                if (is_callable($closure)) {
                    $closure();
                }
            } finally {
                $scriptOutput = ob_get_clean();
            }
            if (!empty($scriptOutput)) {
                Log::info('升级前脚本输出', ['output' => trim($scriptOutput)]);
            }
        } catch (\Throwable $e) {
            throw new \RuntimeException('升级前脚本执行失败：' . $e->getMessage());
        }
    }

    protected function executeAfterScript(string $extractDir, array $upgradeJson): void
    {
        if (empty($upgradeJson['has_after_script'])) {
            return;
        }

        $scriptPath = $extractDir . '/scripts/after_upgrade.php';
        if (!File::exists($scriptPath)) {
            return;
        }

        try {
            // 用输出缓冲吞掉脚本内的 echo 输出，避免污染接口 JSON 响应
            ob_start();
            try {
                $closure = require $scriptPath;
                if (is_callable($closure)) {
                    $closure();
                }
            } finally {
                $scriptOutput = ob_get_clean();
            }
            if (!empty($scriptOutput)) {
                Log::info('升级后脚本输出', ['output' => trim($scriptOutput)]);
            }
        } catch (\Throwable $e) {
            Log::warning('升级后脚本执行失败', ['error' => $e->getMessage()]);
        }
    }

    protected function updateSystemVersion(string $newVersion, array $upgradeJson, string $backupPath): void
    {
        SystemVersion::where('status', 1)->update(['status' => 0]);

        $build = $upgradeJson['build'] ?? date('Ymd');

        // 以 version + build 为唯一键，重复升级同一版本时更新记录，避免唯一约束冲突
        SystemVersion::updateOrCreate(
            [
                'version' => $newVersion,
                'build' => $build,
            ],
            [
                'changelog' => $upgradeJson['changelog'] ?? null,
                'upgrade_type' => '手动升级',
                'status' => 1,
                'operator_id' => Auth::guard('admin')->id(),
                'backup_path' => $backupPath,
                'upgrade_result' => 1,
                'detail' => [
                    'files_add' => count($upgradeJson['files']['add'] ?? []),
                    'files_modify' => count($upgradeJson['files']['modify'] ?? []),
                    'files_delete' => count($upgradeJson['files']['delete'] ?? []),
                    'migrations' => count($upgradeJson['migrations'] ?? []),
                ],
            ]
        );
    }

    public function executeRollback(string $taskId, int $failedStepIndex): void
    {
        $this->updateUpgradeTask($taskId, ['status' => 'rolling_back']);

        try {
            $task = $this->getUpgradeTask($taskId);
            $upgradePath = $task['upgrade_path'];
            $step = $upgradePath[$failedStepIndex];

            $currentVersion = SystemVersion::getCurrentVersionString();

            $failedRecord = SystemVersion::where('status', 1)->orderBy('id', 'desc')->first();
            if ($failedRecord) {
                $backupPath = $failedRecord->backup_path;
                if ($backupPath && File::exists($backupPath)) {
                    $extracted = $this->packageService->extractPackage(
                        UpgradePackage::findByVersions($step['from'], $step['to'])?->package_path ?? ''
                    );
                    $upgradeJson = $extracted ? $extracted['upgrade_json'] : [];

                    $this->backupService->restoreFromBackup($backupPath, $upgradeJson);
                }

                $failedRecord->update([
                    'status' => 0,
                    'upgrade_result' => 0,
                    'error_message' => $task['error_message'],
                ]);
            }

            $previousRecord = SystemVersion::where('version', $step['from'])
                ->orderBy('id', 'desc')
                ->first();

            if ($previousRecord) {
                $previousRecord->update(['status' => 1]);
            }

            Cache::forget('system_current_version');
            Cache::forget('system_current_build');
            Artisan::call('cache:clear');

            $this->updateUpgradeTask($taskId, ['status' => 'rolled_back']);
        } catch (\Throwable $e) {
            Log::error('回滚失败', ['task_id' => $taskId, 'error' => $e->getMessage()]);
            $this->updateUpgradeTask($taskId, ['status' => 'rollback_failed']);
        }
    }

    public function rollback(): array
    {
        $lock = Cache::lock('system_upgrade', 3600);
        if (!$lock->get()) {
            return ApiResponse::error(60013, '当前有升级任务正在执行');
        }

        try {
            $current = SystemVersion::getCurrentVersion();
            if (!$current) {
                $lock->release();
                return ApiResponse::error(60010, '无法获取当前版本信息');
            }

            if (!$current->backup_path || !File::exists($current->backup_path)) {
                $lock->release();
                return ApiResponse::error(60010, '没有可用的备份文件');
            }

            $previousVersion = SystemVersion::where('id', '!=', $current->id)
                ->where('version', '!=', $current->version)
                ->orderBy('id', 'desc')
                ->first();

            if (!$previousVersion) {
                $lock->release();
                return ApiResponse::error(60010, '没有可回滚的版本');
            }

            $this->backupService->restoreFromBackup($current->backup_path, []);

            $current->update(['status' => 0, 'upgrade_result' => 0]);
            $previousVersion->update(['status' => 1]);

            Cache::forget('system_current_version');
            Cache::forget('system_current_build');
            Artisan::call('cache:clear');

            $lock->release();

            return ApiResponse::success([
                'current_version' => $current->version,
                'rollback_to' => $previousVersion->version,
                'status' => 'rolled_back',
            ], '回滚成功');
        } catch (\Throwable $e) {
            $lock->release();
            return ApiResponse::error(60010, '回滚失败：' . $e->getMessage());
        }
    }

    protected function preparePackages(array &$upgradePath): void
    {
        foreach ($upgradePath as &$step) {
            $localPackage = UpgradePackage::findByVersions($step['from'], $step['to']);
            if ($localPackage) {
                $step['source'] = 'local';
                continue;
            }

            if (!empty($step['download_url'])) {
                $result = $this->packageService->downloadRemotePackage(
                    $step['download_url'],
                    $step['from'],
                    $step['to']
                );
                if ($result['code'] === 0) {
                    $step['source'] = 'remote';
                }
            }
        }
    }

    public function getUpgradeStatus(): array
    {
        $task = $this->findLatestTask();

        if (!$task) {
            return ApiResponse::success([
                'task_id' => null,
                'status' => 'idle',
                'current_step' => null,
                'completed_steps' => [],
                'remaining_steps' => [],
                'progress' => 0,
                'error_message' => null,
            ]);
        }

        // 任务已终态但锁仍残留，自动释放
        $terminalStatuses = ['success', 'failed', 'rolled_back', 'rollback_failed'];
        if (in_array($task['status'], $terminalStatuses)) {
            $lock = Cache::lock('system_upgrade', 3600);
            try {
                $lock->release();
            } catch (\Throwable $e) {
                // 忽略
            }
        }

        $totalSteps = count($task['upgrade_path'] ?? []);
        $completedCount = count($task['completed_steps'] ?? []);
        $progress = $totalSteps > 0 ? round(($completedCount / $totalSteps) * 100) : 0;

        return ApiResponse::success([
            'task_id' => $task['task_id'],
            'status' => $task['status'],
            'current_step' => $task['current_step_index'] ?? null,
            'completed_steps' => $task['completed_steps'] ?? [],
            'remaining_steps' => $task['remaining_steps'] ?? [],
            'progress' => (int) $progress,
            'error_message' => $task['error_message'] ?? null,
        ]);
    }

    protected function saveUpgradeTask(array $task): void
    {
        $taskDir = storage_path('app_versionmgr');
        if (!File::isDirectory($taskDir)) {
            File::makeDirectory($taskDir, 0755, true);
        }

        File::put($taskDir . '/upgrade_task.json', json_encode($task, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
    }

    protected function getUpgradeTask(string $taskId): ?array
    {
        $taskFile = storage_path('app_versionmgr/upgrade_task.json');
        if (!File::exists($taskFile)) {
            return null;
        }

        $task = json_decode(File::get($taskFile), true);
        return $task && $task['task_id'] === $taskId ? $task : null;
    }

    protected function updateUpgradeTask(string $taskId, array $updates): void
    {
        $task = $this->getUpgradeTask($taskId);
        if (!$task) {
            return;
        }

        $task = array_merge($task, $updates);
        $this->saveUpgradeTask($task);
    }

    protected function findLatestTask(): ?array
    {
        $taskFile = storage_path('app_versionmgr/upgrade_task.json');
        if (!File::exists($taskFile)) {
            return null;
        }

        return json_decode(File::get($taskFile), true);
    }

    /**
     * 强制清除升级锁，同时处理 cache 表和 cache_locks 表
     */
    protected function forceClearUpgradeLock(): void
    {
        // 清除缓存 key
        Cache::forget('system_upgrade');

        // database 驱动下，锁存储在 cache_locks 表，直接删除所有可能的锁记录
        try {
            $store = Cache::getStore();
            if ($store instanceof \Illuminate\Cache\DatabaseStore) {
                $connection = $store->getConnection();
                // 清除所有包含 system_upgrade 的锁记录（含前缀）
                $connection->table('cache_locks')
                    ->where('key', 'like', '%system_upgrade%')
                    ->delete();
                // 也清除以锁 key 命名的缓存条目
                $connection->table('cache')
                    ->where('key', 'like', '%system_upgrade%')
                    ->delete();
            }
        } catch (\Throwable $e) {
            Log::warning('强制清除升级锁失败', ['error' => $e->getMessage()]);
        }
    }
}
