<?php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class DeployUpgrade extends Command
{
    protected $signature = 'upgrade:deploy
                            {package : 升级包名称（如 cmspro-5.0.0-to-5.1.0 或目录路径）}
                            {--skip-backup : 跳过备份步骤}
                            {--skip-migrate : 跳过数据库迁移}
                            {--dry-run : 仅输出部署计划，不实际执行}';

    protected $description = '部署升级包到当前系统（cmspro格式，与VersionManager对齐）';

    private string $codeRoot;
    private string $upgradeRoot;

    public function handle(): int
    {
        $this->codeRoot = base_path();
        $this->upgradeRoot = storage_path('upgrades');

        $packageArg = $this->argument('package');
        $dryRun = $this->option('dry-run');

        $packageDir = $this->resolvePackageDir($packageArg);
        $upgradeJsonPath = $packageDir . '/upgrade.json';

        if (!File::isDirectory($packageDir)) {
            $this->error("升级包目录不存在：{$packageDir}");
            return self::FAILURE;
        }

        if (!File::exists($upgradeJsonPath)) {
            $this->error("upgrade.json 不存在：{$upgradeJsonPath}");
            return self::FAILURE;
        }

        $manifest = json_decode(File::get($upgradeJsonPath), true);

        $this->info("=== 部署升级包 ===");
        $this->info("版本：{$manifest['from_version']} → {$manifest['to_version']}");
        $this->info("构建号：{$manifest['build']}");
        $this->info("发布日期：{$manifest['release_date']}");
        if (!empty($manifest['changelog'])) {
            $this->info("描述：{$manifest['changelog']}");
        }
        $this->newLine();

        $this->displayDeploymentPlan($manifest);

        if ($dryRun) {
            $this->info('（dry-run 模式，未实际执行）');
            return self::SUCCESS;
        }

        if (!$this->confirm('确认执行升级部署？')) {
            $this->info('已取消。');
            return self::SUCCESS;
        }

        if (!$this->option('skip-backup')) {
            $this->executeBackup($manifest);
        }

        $this->executeBeforeScript($packageDir, $manifest);
        $this->deployFiles($packageDir, $manifest);

        if (!$this->option('skip-migrate')) {
            $this->executeMigrate($packageDir, $manifest);
        }

        $this->executeAfterScript($packageDir, $manifest);
        $this->rebuildCache();

        $this->info("升级部署完成：v{$manifest['to_version']}");
        return self::SUCCESS;
    }

    private function resolvePackageDir(string $packageArg): string
    {
        if (File::isDirectory($packageArg) && File::exists($packageArg . '/upgrade.json')) {
            return realpath($packageArg);
        }

        $inUpgrades = $this->upgradeRoot . '/' . $packageArg;
        if (File::isDirectory($inUpgrades) && File::exists($inUpgrades . '/upgrade.json')) {
            return realpath($inUpgrades);
        }

        $zipPath = $this->upgradeRoot . '/' . $packageArg . '.zip';
        if (File::exists($zipPath)) {
            return $this->extractZip($zipPath, $this->upgradeRoot);
        }

        return $packageArg;
    }

    private function extractZip(string $zipPath, string $destDir): string
    {
        $zip = new \ZipArchive();
        if ($zip->open($zipPath) !== true) {
            $this->error("无法打开ZIP文件：{$zipPath}");
            return '';
        }

        $zip->extractTo($destDir);
        $zip->close();

        $packageName = pathinfo($zipPath, PATHINFO_FILENAME);
        return $destDir . '/' . $packageName;
    }

    private function displayDeploymentPlan(array $manifest): void
    {
        $this->info('--- 部署计划 ---');

        $addCount = count($manifest['files']['add'] ?? []);
        $modCount = count($manifest['files']['modify'] ?? []);
        $delCount = count($manifest['files']['delete'] ?? []);
        $totalFiles = $addCount + $modCount + $delCount;

        $this->info("变更文件数：{$totalFiles}（新增 {$addCount} | 修改 {$modCount} | 删除 {$delCount}）");

        $migCount = count($manifest['migrations'] ?? []);
        if ($migCount > 0) {
            $this->info("数据库迁移：{$migCount} 个");
        }

        if (!empty($manifest['has_before_script'])) {
            $this->info('包含 before_upgrade 脚本');
        }
        if (!empty($manifest['has_after_script'])) {
            $this->info('包含 after_upgrade 脚本');
        }

        $this->newLine();
    }

    private function executeBackup(array $manifest): void
    {
        $this->info('正在备份受影响的代码文件...');

        $backupDir = $this->upgradeRoot . '/backups';
        File::ensureDirectoryExists($backupDir);

        $timestamp = date('Ymd_His');
        $backupFile = $backupDir . "/pre_{$manifest['to_version']}_{$timestamp}.json";

        $backupInfo = [
            'version' => $manifest['to_version'],
            'from_version' => $manifest['from_version'],
            'backup_time' => date('Y-m-d H:i:s'),
            'files' => [],
        ];

        $targets = array_merge(
            $manifest['files']['modify'] ?? [],
            $manifest['files']['delete'] ?? []
        );

        foreach ($targets as $relativePath) {
            $fullPath = $this->codeRoot . '/' . $relativePath;
            if (File::exists($fullPath)) {
                $backupInfo['files'][$relativePath] = base64_encode(File::get($fullPath));
            }
        }

        File::put($backupFile, json_encode($backupInfo, JSON_UNESCAPED_SLASHES));
        $this->info("备份已保存：" . basename($backupFile));
    }

    private function executeBeforeScript(string $packageDir, array $manifest): void
    {
        if (empty($manifest['has_before_script'])) return;

        $scriptPath = $packageDir . '/scripts/before_upgrade.php';
        if (File::exists($scriptPath)) {
            $this->info('执行 before_upgrade 脚本...');
            $this->executePhpScript($scriptPath);
        }
    }

    private function deployFiles(string $packageDir, array $manifest): void
    {
        $copied = 0;

        $allFiles = array_merge(
            $manifest['files']['add'] ?? [],
            $manifest['files']['modify'] ?? []
        );

        $this->output->progressStart(count($allFiles));

        foreach ($allFiles as $relativePath) {
            $sourcePath = $packageDir . '/files/add/' . $relativePath;
            if (!File::exists($sourcePath)) {
                $sourcePath = $packageDir . '/files/modify/' . $relativePath;
            }

            if (!File::exists($sourcePath)) {
                $this->warn("升级包中缺少文件：{$relativePath}");
                $this->output->progressAdvance();
                continue;
            }

            $destPath = $this->codeRoot . '/' . $relativePath;
            File::ensureDirectoryExists(dirname($destPath));
            File::copy($sourcePath, $destPath);
            $copied++;
            $this->output->progressAdvance();
        }

        $this->output->progressFinish();

        $deletedFiles = $manifest['files']['delete'] ?? [];
        foreach ($deletedFiles as $relativePath) {
            $fullPath = $this->codeRoot . '/' . $relativePath;
            if (File::exists($fullPath)) {
                File::delete($fullPath);
                $this->line("已删除：{$relativePath}");
            }
        }

        $this->info("文件覆盖完成：{$copied} 个文件");
    }

    private function executeMigrate(string $packageDir, array $manifest): void
    {
        $migrations = $manifest['migrations'] ?? [];

        if (empty($migrations)) {
            $this->info('无数据库迁移需要执行');
            return;
        }

        $this->info('执行数据库迁移...');

        $migrationsDir = $packageDir . '/migrations';
        if (!File::isDirectory($migrationsDir)) {
            $this->warn('升级包中无 migrations 目录');
            return;
        }

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

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

        $this->info('数据库迁移执行完成');
    }

    private function executeAfterScript(string $packageDir, array $manifest): void
    {
        if (empty($manifest['has_after_script'])) return;

        $scriptPath = $packageDir . '/scripts/after_upgrade.php';
        if (File::exists($scriptPath)) {
            $this->info('执行 after_upgrade 脚本...');
            $this->executePhpScript($scriptPath);
        }
    }

    private function rebuildCache(): void
    {
        $this->info('重建缓存...');
        $this->call('config:cache');
        $this->call('route:cache');
        $this->call('view:cache');
    }

    private function executePhpScript(string $scriptPath): void
    {
        $phpBin = PHP_BINARY;
        exec("{$phpBin} \"{$scriptPath}\"", $output, $result);
        foreach ($output as $line) $this->line("  {$line}");
        if ($result !== 0) $this->warn("脚本返回非零状态码：{$result}");
    }
}
