<?php

namespace Kylin\Modules\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Filesystem\Filesystem;
use Kylin\Modules\ModuleManager;

/**
 * 模块备份命令
 *
 * 备份模块的当前状态
 */
class ModuleBackupCommand extends Command
{
    /**
     * 命令名称和签名
     *
     * @var string
     * 
     * 命令参数说明：
     * 1. {modules?*} - 模块名称列表（可选）
     *    - 数据类型：字符串数组
     *    - 用途：指定要备份的模块名称列表，不指定则备份所有模块
     *    - 示例：php artisan module:backup game user
     * 
     * 命令选项说明：
     * 1. {--all} - 备份所有模块
     *    - 完整形式：--all
     *    - 用途：备份所有模块
     *    - 示例：php artisan module:backup --all
     * 
     * 2. {--path=} - 备份路径
     *    - 完整形式：--path
     *    - 数据类型：字符串
     *    - 用途：指定备份路径，默认为 storage/app/module-backups
     *    - 示例：php artisan module:backup game --path=custom/backups
     * 
     * 3. {--name=} - 备份名称
     *    - 完整形式：--name
     *    - 数据类型：字符串
     *    - 用途：指定备份名称，默认为模块名称_时间戳
     *    - 示例：php artisan module:backup game --name=game_backup
     * 
     * 4. {--rollback=} - 从备份回滚模块
     *    - 完整形式：--rollback
     *    - 数据类型：字符串
     *    - 用途：指定要回滚的备份名称
     *    - 示例：php artisan module:backup --rollback=game_20240101_120000
     * 
     * 5. {--list} - 列出可用的备份
     *    - 完整形式：--list
     *    - 用途：列出所有可用的备份
     *    - 示例：php artisan module:backup --list
     */
    protected $signature = 'module:backup {modules?* : 模块名称列表} {--all : 备份所有模块} {--path= : 备份路径，默认为 storage/app/module-backups} {--name= : 备份名称，默认为模块名称_时间戳} {--rollback= : 从备份回滚模块} {--list : 列出可用的备份}';

    /**
     * 命令描述
     *
     * @var string
     */
    protected $description = '备份和回滚模块';

    /**
     * 文件系统实例
     *
     * @var Filesystem
     */
    private readonly Filesystem $files;

    /**
     * 创建新命令实例
     *
     * @param  ModuleManager  $moduleManager  模块管理器实例
     * @param  Filesystem  $files  文件系统实例
     */
    public function __construct(
        private readonly ModuleManager $moduleManager,
        Filesystem $files
    ) {
        parent::__construct();
        $this->files = $files;
    }

    /**
     * 执行控制台命令
     */
    public function handle(): int
    {
        $modules = $this->argument('modules') ?: [];
        $all = $this->option('all');
        $backupPath = $this->option('path') ?: storage_path('app/module-backups');
        $backupName = $this->option('name');
        $rollbackBackup = $this->option('rollback');
        $listBackups = $this->option('list');

        // 确保备份目录存在
        if (!$this->files->exists($backupPath)) {
            $this->files->makeDirectory($backupPath, 0755, true);
        }

        // 处理列出备份
        if ($listBackups) {
            return $this->listBackups($backupPath);
        }

        // 处理回滚
        if ($rollbackBackup) {
            return $this->rollbackFromBackup($backupPath, (string) $rollbackBackup);
        }

        // 确定要备份的模块
        $targetModules = [];
        if ($all) {
            $targetModules = $this->moduleManager->all();
        } elseif (!empty($modules)) {
            foreach ($modules as $moduleName) {
                $module = $this->moduleManager->get($moduleName);
                if ($module) {
                    $targetModules[] = $module;
                } else {
                    $this->warn("模块 [{$moduleName}] 未找到，将被跳过。");
                }
            }
        } else {
            $this->error('请指定模块名称或使用 --all 选项');
            return 1;
        }

        if (empty($targetModules)) {
            $this->error('没有找到要备份的模块');
            return 1;
        }

        // 执行备份
        $this->info('正在备份模块...');
        $this->line('');

        $successCount = 0;
        $failureCount = 0;

        foreach ($targetModules as $module) {
            try {
                $backupFile = $this->backupModule($module, $backupPath, $backupName);
                $this->info("模块 [{$module->getName()}] 已成功备份到: {$backupFile}");
                $successCount++;
            } catch (\Exception $e) {
                $this->error("备份模块 [{$module->getName()}] 时出错: {$e->getMessage()}");
                $failureCount++;
            }
        }

        $this->line('');
        $this->info("备份完成：");
        $this->info("成功: {$successCount}");
        $this->info("失败: {$failureCount}");

        return $failureCount > 0 ? 1 : 0;
    }

    /**
     * 备份模块
     *
     * @param  mixed  $module  模块实例
     * @param  string  $backupPath  备份路径
     * @param  string|null  $backupName  备份名称
     * @return string  备份文件路径
     * @throws \Exception
     */
    protected function backupModule($module, string $backupPath, ?string $backupName): string
    {
        $moduleName = $module->getName();
        $timestamp = date('YmdHis');
        $backupFileName = $backupName ?: "{$moduleName}_{$timestamp}";
        $backupFile = "{$backupPath}/{$backupFileName}.zip";

        // 创建临时目录
        $tempDir = sys_get_temp_dir() . '/module-backup-' . uniqid();
        $this->files->makeDirectory($tempDir, 0755, true);

        try {
            // 复制模块文件到临时目录
            $modulePath = $module->getPath();
            $this->copyDirectory($modulePath, $tempDir);

            // 创建压缩文件
            $this->createZipArchive($tempDir, $backupFile);

            return $backupFile;
        } finally {
            // 清理临时目录
            $this->files->deleteDirectory($tempDir);
        }
    }

    /**
     * 从备份回滚模块
     *
     * @param  string  $backupPath  备份路径
     * @param  string  $backupName  备份名称
     * @return int  退出码
     */
    protected function rollbackFromBackup(string $backupPath, string $backupName): int
    {
        $backupFile = "{$backupPath}/{$backupName}.zip";

        if (!$this->files->exists($backupFile)) {
            $this->error("备份文件未找到: {$backupFile}");
            return 1;
        }

        // 提取模块名称
        $moduleName = explode('_', $backupName)[0];

        // 检查模块是否存在
        $module = $this->moduleManager->get($moduleName);
        if (!$module) {
            $this->error("模块 [{$moduleName}] 未找到。");
            return 1;
        }

        try {
            $this->info("正在从备份 [{$backupName}] 回滚模块 [{$moduleName}]...");

            // 创建临时目录
            $tempDir = sys_get_temp_dir() . '/module-rollback-' . uniqid();
            $this->files->makeDirectory($tempDir, 0755, true);

            // 解压备份文件
            $this->extractZipArchive($backupFile, $tempDir);

            // 清理模块目录
            $modulePath = $module->getPath();
            $this->files->deleteDirectory($modulePath);
            $this->files->makeDirectory($modulePath, 0755, true);

            // 复制回滚文件到模块目录
            $this->copyDirectory($tempDir, $modulePath);

            // 清理临时目录
            $this->files->deleteDirectory($tempDir);

            // 清除模块缓存
            $this->call('module:clear-cache');

            $this->info("模块 [{$moduleName}] 已成功从备份 [{$backupName}] 回滚。");

            return 0;
        } catch (\Exception $e) {
            $this->error("回滚模块时出错: {$e->getMessage()}");
            return 1;
        }
    }

    /**
     * 列出可用的备份
     *
     * @param  string  $backupPath  备份路径
     * @return int  退出码
     */
    protected function listBackups(string $backupPath): int
    {
        $backups = $this->files->glob("{$backupPath}/*.zip");

        if (empty($backups)) {
            $this->info('没有找到备份文件。');
            return 0;
        }

        $this->info('可用的备份文件：');
        $this->line('');

        $headers = ['备份名称', '模块', '日期', '大小'];
        $rows = [];

        foreach ($backups as $backup) {
            $fileName = basename($backup, '.zip');
            $parts = explode('_', $fileName);
            $moduleName = $parts[0];
            $timestamp = isset($parts[1]) ? $parts[1] : '';
            $date = $timestamp ? date('Y-m-d H:i:s', strtotime($timestamp)) : '未知';
            $size = $this->formatFileSize(filesize($backup));

            $rows[] = [$fileName, $moduleName, $date, $size];
        }

        $this->table($headers, $rows);

        return 0;
    }

    /**
     * 复制目录
     *
     * @param  string  $source  源目录
     * @param  string  $target  目标目录
     */
    protected function copyDirectory(string $source, string $target): void
    {
        if (!$this->files->isDirectory($source)) {
            return;
        }

        // 创建目标目录
        if (!$this->files->exists($target)) {
            $this->files->makeDirectory($target, 0755, true);
        }

        // 获取源目录中的文件和子目录
        $files = $this->files->files($source);
        $directories = $this->files->directories($source);

        // 复制文件
        foreach ($files as $file) {
            $targetFile = $target . '/' . $file->getFilename();
            $this->files->copy($file, $targetFile);
        }

        // 递归复制子目录
        foreach ($directories as $directory) {
            $dirName = basename($directory);
            $targetDir = $target . '/' . $dirName;
            $this->copyDirectory($directory, $targetDir);
        }
    }

    /**
     * 创建 ZIP 压缩文件
     *
     * @param  string  $source  源目录
     * @param  string  $target  目标 ZIP 文件
     * @throws \Exception
     */
    protected function createZipArchive(string $source, string $target): void
    {
        $zip = new \ZipArchive();

        if (!$zip->open($target, \ZipArchive::CREATE | \ZipArchive::OVERWRITE)) {
            throw new \Exception("无法创建压缩文件: {$target}");
        }

        // 递归添加文件到 ZIP
        $this->addFilesToZip($zip, $source, '');

        if (!$zip->close()) {
            throw new \Exception("无法关闭压缩文件: {$target}");
        }
    }

    /**
     * 添加文件到 ZIP 压缩文件
     *
     * @param  \ZipArchive  $zip  ZIP 实例
     * @param  string  $source  源目录
     * @param  string  $parentPath  父路径
     */
    protected function addFilesToZip(\ZipArchive $zip, string $source, string $parentPath): void
    {
        $files = $this->files->files($source);
        $directories = $this->files->directories($source);

        // 添加文件
        foreach ($files as $file) {
            $filePath = $parentPath . '/' . $file->getFilename();
            $zip->addFile($file, ltrim($filePath, '/'));
        }

        // 递归添加子目录
        foreach ($directories as $directory) {
            $dirName = basename($directory);
            $newParentPath = $parentPath . '/' . $dirName;
            $this->addFilesToZip($zip, $directory, $newParentPath);
        }
    }

    /**
     * 解压 ZIP 压缩文件
     *
     * @param  string  $file  ZIP 文件路径
     * @param  string  $target  目标目录
     * @throws \Exception
     */
    protected function extractZipArchive(string $file, string $target): void
    {
        $zip = new \ZipArchive();

        if (!$zip->open($file)) {
            throw new \Exception("无法打开压缩文件: {$file}");
        }

        if (!$zip->extractTo($target)) {
            throw new \Exception("无法解压文件: {$file}");
        }

        if (!$zip->close()) {
            throw new \Exception("无法关闭压缩文件: {$file}");
        }
    }

    /**
     * 格式化文件大小
     *
     * @param  int  $size  文件大小（字节）
     * @return string  格式化后的文件大小
     */
    protected function formatFileSize(int $size): string
    {
        $units = ['B', 'KB', 'MB', 'GB'];
        $unitIndex = 0;

        while ($size > 1024 && $unitIndex < count($units) - 1) {
            $size /= 1024;
            $unitIndex++;
        }

        return round($size, 2) . ' ' . $units[$unitIndex];
    }
}
