<?php
// +----------------------------------------------------------------------
// | onezan-admin 快速开发的管理平台
// +----------------------------------------------------------------------
// | 官方网址：https://www.onezan.cn
// +----------------------------------------------------------------------
// | onezan团队 版权所有 开源版本可自由商用
// +----------------------------------------------------------------------
// | Author: 543885307@qq.com
// +----------------------------------------------------------------------
namespace app\adminapi\controller\system;

use app\service\domain\queue\QueueConfigService;
use Core\service\queue\QueueDispatcher;
use app\model\upgrade\UpgradeLog;
use app\service\domain\upgrade\UpgradeChecker;
use app\service\domain\upgrade\UpgradeTokenService;
use GuzzleHttp\Client;
use think\facade\Log;
use think\Request;


/**
 * 在线升级控制器
 * 负责框架和应用的版本升级：状态查询、包上传、执行升级、进度查询、回滚
 */
class Upgrade
{
    private const UPGRADE_QUEUE = 'upgrade';

    /**
     * 获取升级状态
     * 返回当前版本、远程版本、队列状态
     * @param UpgradeChecker $checker
     * @return \think\response\Json
     */
    public function status(UpgradeChecker $checker)
    {
        $current = $checker->currentVersion();
        $remote = $checker->checkRemote($current['version']);
        $queue = $this->queueStatus();

        return json([
            'code' => 0,
            'message' => 'ok',
            'data' => [
                'current' => $current,
                'remote' => $remote,
                'queue' => $queue,
            ],
        ]);
    }

    /**
     * @param Request $request
     * @param UpgradeTokenService $tokenService
     * @return \think\response\Json
     */
    public function verifyToken(Request $request, UpgradeTokenService $tokenService)
    {
        $code = trim((string)$request->post('code', ''));
        $type = trim((string)$request->post('type', 'framework'));
        $appCode = trim((string)$request->post('industry_code', ''));

        if ($code === '') {
            return json(['code' => -1, 'message' => '']);
        }

        try {
            $config = $tokenService->remoteConfig();
            $apiUrl = $config['api_url'];

            if ($apiUrl === '') {
                return json(['code' => -1, 'message' => '']);
            }

            $domain = $_SERVER['HTTP_HOST'] ?? 'localhost';
            $sign = md5($code . $domain . $config['secret']);
            $url = rtrim($apiUrl, '/') . '?action=verify&code=' . urlencode($code) . '&domain=' . urlencode($domain) . '&ip=' . urlencode($_SERVER['SERVER_ADDR'] ?? '0.0.0.0') . '&sign=' . $sign;

            $client = new Client(['timeout' => 15]);
            $response = $client->get($url);
            $body = json_decode((string)$response->getBody(), true);

            if (!isset($body['code']) || $body['code'] !== 0) {
                return json(['code' => -1, 'message' => $body['message'] ?? '']);
            }

            $remoteData = $body['data'] ?? [];
            $tokenService->save($code, $domain, $type, $appCode);
            $tokenService->updateRemoteVerification($code, $remoteData);

            return json([
                'code' => 0,
                'message' => '',
                'data' => $remoteData,
            ]);
        } catch (\Throwable $e) {
            Log::error('upgrade token verify error: ' . $e->getMessage());
            return json(['code' => -1, 'message' => 'Token verify failed: ' . $e->getMessage()]);
        }
    }

    /**
     * @param UpgradeTokenService $tokenService
     * @return \think\response\Json
     */
    public function tokenInfo(UpgradeTokenService $tokenService)
    {
        $token = $tokenService->currentToken();
        return json([
            'code' => 0,
            'message' => 'ok',
            'data' => $token,
        ]);
    }

    /**
     * 提交升级请求
     * @param Request $request
     * @param UpgradeTokenService $tokenService
     * @return \think\response\Json
     */
    public function submit(Request $request, UpgradeTokenService $tokenService, UpgradeChecker $checker)
    {
        $type = trim((string)$request->post('type', 'framework'));
        $version = trim((string)$request->post('version', ''));
        $appCode = trim((string)$request->post('industry_code', ''));
        $updateFrontend = (int)$request->post('update_frontend', 1);
        $queue = $this->queueStatus();

        if ($version === '') {
            return json(['code' => -1, 'message' => '']);
        }

        if (!(bool)($queue['async_ready'] ?? false)) {
            // Fallback: execute synchronously when queue is not available
            try {
                app()->make(\app\service\domain\upgrade\handler\UpgradeExecuteHandler::class)->handle($jobData);
            } catch (\Throwable $e) {
                // Already logged by handler
            }
            return json([
                'code' => 0,
                'message' => '',
                'data' => ['log_id' => $log->id],
            ]);
        }

        $currentVersion = $this->getCurrentVersion($type, $appCode);
        if (version_compare($currentVersion, $version, '>=')) {
            return json(['code' => -1, 'message' => " {$currentVersion} "]);
        }

        $token = $tokenService->currentToken();
        if (!$token) {
            return json(['code' => -1, 'message' => 'Unknown error']);
        }

        $isValid = $tokenService->validateLocal($token['code'], $_SERVER['HTTP_HOST'] ?? '');
        if (!$isValid) {
            return json(['code' => -1, 'message' => 'Unknown error']);
        }

        // Save upgrade log
        $log = new UpgradeLog();
        $log->token_id = (int)$token['id'];
        $log->type = $type;
        $log->industry_code = $appCode;
        $log->from_version = $this->getCurrentVersion($type, $appCode);
        $log->to_version = $version;
        $log->status = 0;
        $log->progress = 0;
        $log->message = '';
        $log->create_time = date('Y-m-d H:i:s');
        $log->save();

        // Build job data
        $jobData = [
            'log_id' => $log->id,
            'type' => $type,
            'version' => $version,
            'industry_code' => $appCode,
            'code' => $token['code'],
            'update_frontend' => $updateFrontend,
        ];

        /** @var QueueDispatcher $queue */
        $queue = app()->make(QueueDispatcher::class);
        $queue->dispatch('upgrade.execute', $jobData, [
            'queue' => self::UPGRADE_QUEUE,
            'biz_key' => 'upgrade.execute:' . $log->id,
            'max_attempts' => 1,
        ]);

        return json([
            'code' => 0,
            'message' => '',
            'data' => ['log_id' => $log->id],
        ]);
    }

    /**
     * 查询升级进度
     * @param Request $request
     * @return \think\response\Json
     */
    public function progress(Request $request)
    {
        $logId = (int)$request->get('log_id', 0);
        if ($logId > 0) {
            $log = UpgradeLog::find($logId);
            if ($log) {
                return json([
                    'code' => 0,
                    'message' => 'ok',
                    'data' => $log->toArray(),
                ]);
            }
        }

        $log = UpgradeLog::order('id', 'desc')->find();
        return json([
            'code' => 0,
            'message' => 'ok',
            'data' => $log ? $log->toArray() : null,
        ]);
    }

    /**
     * 查询升级历史
     * @param Request $request
     * @return \think\response\Json
     */
    public function history(Request $request)
    {
        $page = (int)$request->get('page', 1);
        $limit = (int)$request->get('limit', 20);

        $list = UpgradeLog::order('id', 'desc')
            ->paginate([
                'page' => $page,
                'list_rows' => $limit,
            ]);

        return json([
            'code' => 0,
            'message' => 'ok',
            'data' => [
                'list' => $list->items(),
                'total' => (int)$list->total(),
            ],
        ]);
    }

    /**
     * 回滚升级
     * @param Request $request
     * @return \think\response\Json
     */
    public function rollback(Request $request)
    {
        $logId = (int)$request->post('log_id', 0);
        $queue = $this->queueStatus();
        if ($logId <= 0) {
            return json(['code' => -1, 'message' => 'Unknown error']);
        }

        if (!(bool)($queue['async_ready'] ?? false)) {
            return json(['code' => -1, 'message' => (string)($queue['message'] ?? 'Unknown error')]);
        }

        $log = UpgradeLog::find($logId);
        if (!$log) {
            return json(['code' => -1, 'message' => 'Unknown error']);
        }
        if ((int)$log->status !== 3) {
            return json(['code' => -1, 'message' => '? ' . $log->message]);
        }

        $backupPath = $log->backup_path;
        if (empty($backupPath) || !is_dir($backupPath)) {
            return json(['code' => -1, 'message' => ': ' . ($backupPath ?: 'Unknown error')]);
        }

        //
        $rollbackLog = new UpgradeLog();
        $rollbackLog->token_id      = $log->token_id;
        $rollbackLog->type          = $log->type;
        $rollbackLog->industry_code = $log->industry_code;
        $rollbackLog->from_version  = $log->to_version;
        $rollbackLog->to_version    = $log->from_version;
        $rollbackLog->status        = 0;
        $rollbackLog->progress      = 0;
        $rollbackLog->message       = '';
        $rollbackLog->backup_path   = $backupPath;
        $rollbackLog->save();

        /** @var QueueDispatcher $queue */
        $queue = app()->make(QueueDispatcher::class);
        $queue->dispatch('upgrade.rollback', [
            'rollback_log_id' => $rollbackLog->id,
            'source_log_id'   => $log->id,
            'type'            => $log->type,
            'from_version'    => $log->from_version,
            'industry_code'   => $log->industry_code,
            'backup_path'     => $backupPath,
        ], [
            'queue' => self::UPGRADE_QUEUE,
            'biz_key' => 'upgrade.rollback:' . $rollbackLog->id,
        ]);

        return json([
            'code'    => 0,
            'message' => '',
            'data'    => ['log_id' => $rollbackLog->id],
        ]);
    }

    /**
     * 获取当前版本
     * @param string $type
     * @param string $appCode
     * @return string
     */
    private function getCurrentVersion(string $type, string $appCode): string
    {
        if ($type === 'framework') {
            $manifestFile = app()->getRootPath() . 'core/manifest/manifest.json';
        } else {
            $manifestFile = app()->getRootPath() . 'applications/' . $appCode . '/manifest.json';
        }

        if (file_exists($manifestFile)) {
            $manifest = json_decode(file_get_contents($manifestFile), true);
            return $manifest['version'] ?? '1.0.0';
        }

        return '1.0.0';
    }

    /**
     * 获取队列状态
     * @return array
     */
    private function queueStatus(): array
    {
        /** @var QueueConfigService $config */
        $config = app()->make(QueueConfigService::class);
        $enabled = $config->isEnabled();
        $driver = $config->driver();
        $asyncReady = $enabled && in_array($driver, ['database', 'redis'], true);

        return [
            'enabled' => $enabled ? 1 : 0,
            'driver' => $driver,
            'async_ready' => $asyncReady ? 1 : 0,
            'upgrade_queue' => self::UPGRADE_QUEUE,
            'default_queue' => $config->defaultQueue(),
            'message' => $asyncReady
                ? '?php think queue:work --queue upgrade '
                : ' database ?redis  php think queue:work --queue upgrade',
        ];
    }
}