<?php

namespace App\Apps\Versionmgr\Services;

use App\Apps\Versionmgr\Models\UpgradePackage;
use App\Http\Responses\ApiResponse;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use ZipArchive;

class PackageService
{
    protected string $packagePath;

    protected string $tempPath;

    public function __construct()
    {
        $this->packagePath = storage_path('app_versionmgr/packages');
        $this->tempPath = storage_path('app_versionmgr/temp');
    }

    public function uploadPackage(UploadedFile $file): array
    {
        if (!File::isDirectory($this->packagePath)) {
            File::makeDirectory($this->packagePath, 0755, true);
        }

        $zipPath = $this->packagePath . '/' . $file->getClientOriginalName();
        $file->move($this->packagePath, $file->getClientOriginalName());

        try {
            $validation = $this->validatePackage($zipPath);
            if ($validation['code'] !== 0) {
                if (File::exists($zipPath)) {
                    File::delete($zipPath);
                }
                return $validation;
            }

            $upgradeJson = $validation['data']['upgrade_json'];

            $checksum = $this->calculateChecksum($zipPath);

            $existing = UpgradePackage::where('from_version', $upgradeJson['from_version'])
                ->where('to_version', $upgradeJson['to_version'])
                ->first();

            if ($existing) {
                if (File::exists($existing->package_path)) {
                    File::delete($existing->package_path);
                }
                $existing->update([
                    'build' => $upgradeJson['build'],
                    'changelog' => $upgradeJson['changelog'] ?? null,
                    'package_path' => $zipPath,
                    'package_size' => File::size($zipPath),
                    'checksum' => $checksum,
                    'source' => 'local',
                    'status' => 1,
                    'operator_id' => Auth::guard('admin')->id(),
                ]);

                return ApiResponse::success($existing, '升级包已更新');
            }

            $package = UpgradePackage::create([
                'from_version' => $upgradeJson['from_version'],
                'to_version' => $upgradeJson['to_version'],
                'build' => $upgradeJson['build'],
                'changelog' => $upgradeJson['changelog'] ?? null,
                'package_path' => $zipPath,
                'package_size' => File::size($zipPath),
                'checksum' => $checksum,
                'source' => 'local',
                'status' => 1,
                'operator_id' => Auth::guard('admin')->id(),
            ]);

            return ApiResponse::success($package, '升级包上传成功');
        } catch (\Throwable $e) {
            if (File::exists($zipPath)) {
                File::delete($zipPath);
            }
            return ApiResponse::error(60001, '升级包处理失败：' . $e->getMessage());
        }
    }

    public function downloadRemotePackage(string $downloadUrl, string $fromVersion, string $toVersion): array
    {
        if (!File::isDirectory($this->packagePath)) {
            File::makeDirectory($this->packagePath, 0755, true);
        }

        $fileName = "cmspro-{$fromVersion}-to-{$toVersion}.zip";
        $zipPath = $this->packagePath . '/' . $fileName;

        try {
            $response = Http::withOptions(['verify' => false])->get($downloadUrl);

            if (!$response->successful()) {
                return ApiResponse::error(60012, '升级包下载失败：HTTP ' . $response->status());
            }

            File::put($zipPath, $response->body());

            $validation = $this->validatePackage($zipPath);
            if ($validation['code'] !== 0) {
                if (File::exists($zipPath)) {
                    File::delete($zipPath);
                }
                return $validation;
            }

            $upgradeJson = $validation['data']['upgrade_json'];
            $checksum = $this->calculateChecksum($zipPath);

            $package = UpgradePackage::create([
                'from_version' => $upgradeJson['from_version'],
                'to_version' => $upgradeJson['to_version'],
                'build' => $upgradeJson['build'],
                'changelog' => $upgradeJson['changelog'] ?? null,
                'package_path' => $zipPath,
                'package_size' => File::size($zipPath),
                'checksum' => $checksum,
                'source' => 'remote',
                'status' => 1,
                'operator_id' => Auth::guard('admin')->id(),
            ]);

            return ApiResponse::success($package, '升级包下载成功');
        } catch (\Throwable $e) {
            if (File::exists($zipPath)) {
                File::delete($zipPath);
            }
            Log::error('远程升级包下载失败', ['error' => $e->getMessage()]);
            return ApiResponse::error(60012, '升级包下载失败：' . $e->getMessage());
        }
    }

    public function validatePackage(string $zipPath): array
    {
        if (!File::exists($zipPath)) {
            return ApiResponse::error(60001, '升级包文件不存在');
        }

        $zip = new ZipArchive;
        if ($zip->open($zipPath) !== true) {
            return ApiResponse::error(60001, '升级包格式无效，无法解压');
        }

        $upgradeContent = $zip->getFromName('upgrade.json');
        if ($upgradeContent === false) {
            $zip->close();
            return ApiResponse::error(60002, '升级包缺少 upgrade.json');
        }

        $upgradeJson = json_decode($upgradeContent, true);
        if (!$upgradeJson || !is_array($upgradeJson)) {
            $zip->close();
            return ApiResponse::error(60002, 'upgrade.json 格式错误');
        }

        $validation = $this->validateUpgradeJson($upgradeJson);
        if ($validation !== null) {
            $zip->close();
            return $validation;
        }

        if (!empty($upgradeJson['checksum'])) {
            $actualChecksum = $this->calculateChecksum($zipPath);
            $expectedChecksum = str_replace('sha256:', '', $upgradeJson['checksum']);
            if ($actualChecksum !== $expectedChecksum && $upgradeJson['checksum'] !== "sha256:{$actualChecksum}") {
                $zip->close();
                return ApiResponse::error(60005, '升级包校验失败（checksum 不匹配）');
            }
        }

        $zip->close();

        return ApiResponse::success(['upgrade_json' => $upgradeJson], '验证通过');
    }

    protected function validateUpgradeJson(array $json): ?array
    {
        $required = ['from_version', 'to_version', 'build'];
        foreach ($required as $field) {
            if (empty($json[$field])) {
                return ApiResponse::error(60002, "upgrade.json 缺少必填字段：{$field}");
            }
        }

        if (!preg_match('/^\d+\.\d+\.\d+/', $json['from_version'])) {
            return ApiResponse::error(60002, 'from_version 版本号格式无效');
        }

        if (!preg_match('/^\d+\.\d+\.\d+/', $json['to_version'])) {
            return ApiResponse::error(60002, 'to_version 版本号格式无效');
        }

        if (isset($json['files'])) {
            $allPaths = array_merge(
                $json['files']['add'] ?? [],
                $json['files']['modify'] ?? [],
                $json['files']['delete'] ?? []
            );

            foreach ($allPaths as $path) {
                if (str_contains($path, '..')) {
                    return ApiResponse::error(60002, "文件路径包含非法字符：{$path}");
                }
                $forbidden = ['.env', 'config/auth.php', 'config/database.php'];
                foreach ($forbidden as $protected) {
                    if (str_starts_with($path, $protected)) {
                        return ApiResponse::error(60002, "禁止修改受保护文件：{$path}");
                    }
                }
            }
        }

        return null;
    }

    public function extractPackage(string $zipPath): ?array
    {
        if (!File::isDirectory($this->tempPath)) {
            File::makeDirectory($this->tempPath, 0755, true);
        }

        $extractDir = $this->tempPath . '/extract_' . time();
        File::makeDirectory($extractDir, 0755, true);

        $zip = new ZipArchive;
        if ($zip->open($zipPath) !== true) {
            return null;
        }
        $zip->extractTo($extractDir);
        $zip->close();

        $upgradeJsonPath = $extractDir . '/upgrade.json';
        if (!File::exists($upgradeJsonPath)) {
            File::deleteDirectory($extractDir);
            return null;
        }

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

        return [
            'extract_dir' => $extractDir,
            'upgrade_json' => $upgradeJson,
        ];
    }

    public function cleanupExtract(string $extractDir): void
    {
        if (File::isDirectory($extractDir)) {
            File::deleteDirectory($extractDir);
        }
    }

    public function deletePackage(int $id): array
    {
        $package = UpgradePackage::find($id);
        if (!$package) {
            return ApiResponse::error(60001, '升级包不存在');
        }

        $lockPath = storage_path('app_versionmgr/upgrade.lock');
        if (File::exists($lockPath)) {
            return ApiResponse::error(60013, '当前有升级任务正在执行，无法删除升级包');
        }

        if (File::exists($package->package_path)) {
            File::delete($package->package_path);
        }

        $package->delete();

        return ApiResponse::success(null, '升级包已删除');
    }

    public function getPackageList(array $params = []): array
    {
        $query = UpgradePackage::orderBy('create_time', 'desc');

        if (!empty($params['from_version'])) {
            $query->where('from_version', $params['from_version']);
        }

        if (!empty($params['to_version'])) {
            $query->where('to_version', $params['to_version']);
        }

        if (isset($params['source'])) {
            $query->where('source', $params['source']);
        }

        $perPage = $params['per_page'] ?? 15;
        $paginator = $query->paginate($perPage);

        return ApiResponse::paginate($paginator);
    }

    protected function calculateChecksum(string $filePath): string
    {
        return hash_file('sha256', $filePath);
    }
}
