<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

namespace App\Services\Plugin;

use App\Exceptions\SupportCenter\SupportCenterException;
use App\Models\HostPluginPackage;
use App\Services\SupportCenter\SupportCenterConnectivityGuard;
use App\Services\SupportCenter\SupportCenterOpenHostEndpoints;
use App\Services\SupportCenter\SupportCenterRequestContextBuilder;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * 下载官方插件安装包并执行校验和/签名校验。
 */
final class HostPluginPackageDownloadService
{
    public function __construct(
        private readonly HostPluginClaimCheckService $claimCheckService,
        private readonly HostPluginDownloadTokenService $downloadTokenService,
        private readonly HostPluginPackageVerifier $packageVerifier,
        private readonly HostPluginPackageSignatureVerifier $signatureVerifier,
        private readonly SupportCenterConnectivityGuard $connectivityGuard,
        private readonly SupportCenterRequestContextBuilder $contextBuilder,
        private readonly HostPluginSupportCenterContextResolver $contextResolver,
        private readonly HostPluginOperationLogger $operationLogger,
    ) {}

    public function downloadAndVerify(
        string $pluginCode,
        string $version,
        HostPluginRemotePackagePurpose $purpose = HostPluginRemotePackagePurpose::Install,
    ): HostPluginPackage {
        $claimResult = $this->claimCheckService->assertClaimAllowed($pluginCode, $version, $purpose);
        $tokenResult = $this->downloadTokenService->requestToken($claimResult, $purpose);
        $package = $tokenResult->package;

        $this->operationLogger->logStarted(
            HostPluginOperationLogger::OPERATION_PACKAGE_DOWNLOAD,
            $pluginCode,
            ['version' => $version, 'request_id' => $tokenResult->requestId],
        );

        $package->fill([
            'download_status' => HostPluginPackage::STATUS_DOWNLOADING,
            'download_message' => null,
            'download_error_code' => null,
        ]);
        $package->save();

        try {
            $this->connectivityGuard->assertRealCallsAllowed('host_plugin_package_download');

            $localPath = $this->downloadPackageFile(
                downloadUrl: $tokenResult->downloadUrl,
                downloadToken: $tokenResult->downloadToken,
                package: $package,
            );

            $package->fill([
                'local_path' => $localPath,
                'local_filename' => basename($localPath),
                'local_size' => is_file($localPath) ? (int) filesize($localPath) : null,
                'downloaded_at' => now()->utc(),
                'download_status' => HostPluginPackage::STATUS_DOWNLOADED,
                'download_message' => null,
                'download_error_code' => null,
                'download_request_id' => $tokenResult->requestId,
            ]);
            $package->save();

            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_PACKAGE_DOWNLOAD,
                $pluginCode,
                null,
                ['version' => $version, 'local_filename' => basename($localPath)],
            );

            $this->operationLogger->logStarted(
                HostPluginOperationLogger::OPERATION_PACKAGE_VERIFICATION,
                $pluginCode,
                ['version' => $version],
            );

            $verifiedPackage = $this->packageVerifier->verify($package->fresh() ?? $package);

            if (! $verifiedPackage->verified) {
                $this->operationLogger->logFailed(
                    HostPluginOperationLogger::OPERATION_PACKAGE_VERIFICATION,
                    $pluginCode,
                    (string) ($verifiedPackage->download_message ?? __('dberp.plugin_center.package.verification_failed')),
                    $verifiedPackage->download_error_code,
                );

                return $verifiedPackage;
            }

            $signedPackage = $this->signatureVerifier->verifyIfConfigured($verifiedPackage->fresh() ?? $verifiedPackage);

            if (! $signedPackage->verified) {
                $this->operationLogger->logFailed(
                    HostPluginOperationLogger::OPERATION_PACKAGE_VERIFICATION,
                    $pluginCode,
                    (string) ($signedPackage->download_message ?? __('dberp.plugin_center.package.verification_failed')),
                    $signedPackage->download_error_code,
                );

                return $signedPackage;
            }

            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_PACKAGE_VERIFICATION,
                $pluginCode,
            );

            return $signedPackage;
        } catch (SupportCenterException $exception) {
            $this->markDownloadFailed($package, $exception->userFacingMessage(), $exception->errorCode(), $tokenResult->requestId);
            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_PACKAGE_DOWNLOAD,
                $pluginCode,
                $exception->userFacingMessage(),
                $exception->errorCode(),
                $tokenResult->requestId,
            );

            throw $exception;
        } catch (Throwable $exception) {
            $message = (string) __('dberp.plugin_center.package.download_failed');
            $this->markDownloadFailed($package, $message, 'package_download_failed', $tokenResult->requestId);
            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_PACKAGE_DOWNLOAD,
                $pluginCode,
                $message,
                'package_download_failed',
                $tokenResult->requestId,
                exception: $exception,
            );

            throw SupportCenterException::remoteServiceUnavailable(
                operation: 'host_plugin_package_download',
                previous: $exception,
            );
        }
    }

    private function downloadPackageFile(
        ?string $downloadUrl,
        string $downloadToken,
        HostPluginPackage $package,
    ): string {
        HostPluginPackageStorage::ensurePackagesDirectoryExists();

        $filename = HostPluginPackageStorage::buildSafeFilename(
            pluginCode: $package->plugin_code,
            version: $package->version,
            originalFilename: $package->package_name,
        );
        $localPath = HostPluginPackageStorage::packagesDirectory().DIRECTORY_SEPARATOR.$filename;

        $url = $this->resolveDownloadUrl($downloadUrl, $downloadToken);
        $timeout = max(30, (int) config('dberp.support_center.timeout', 10));
        $connectTimeout = max(5, (int) config('dberp.support_center.connect_timeout', 5));

        try {
            $response = Http::timeout($timeout)
                ->connectTimeout($connectTimeout)
                ->withOptions(['sink' => $localPath])
                ->accept('*/*')
                ->get($url);
        } catch (ConnectionException $exception) {
            $this->removePartialFile($localPath);
            Log::warning('dberp.plugin_center.package_download.connection_failed', [
                'plugin_code' => $package->plugin_code,
            ]);

            throw SupportCenterException::networkTimeout('host_plugin_package_download', $exception);
        }

        if (! $response->successful()) {
            $this->removePartialFile($localPath);

            throw SupportCenterException::remoteServiceUnavailable(
                httpStatus: $response->status(),
                operation: 'host_plugin_package_download',
            );
        }

        if (! is_file($localPath) || filesize($localPath) === 0) {
            $this->removePartialFile($localPath);

            throw SupportCenterException::invalidResponseFormat(operation: 'host_plugin_package_download');
        }

        return $localPath;
    }

    private function resolveDownloadUrl(?string $downloadUrl, string $downloadToken): string
    {
        if (is_string($downloadUrl) && trim($downloadUrl) !== '') {
            return trim($downloadUrl);
        }

        $baseUrl = rtrim(trim((string) config('dberp.support_center.base_url')), '/');
        $path = SupportCenterOpenHostEndpoints::pluginDownload($downloadToken);
        $query = http_build_query($this->contextBuilder->pluginDownloadQuery(
            siteNo: $this->contextResolver->resolveSiteNo(),
        ));

        return $query === '' ? $baseUrl.$path : $baseUrl.$path.'?'.$query;
    }

    private function markDownloadFailed(
        HostPluginPackage $package,
        string $message,
        ?string $errorCode,
        ?string $requestId,
    ): void {
        $package->fill([
            'download_status' => HostPluginPackage::STATUS_FAILED,
            'download_message' => $message,
            'download_error_code' => $errorCode,
            'download_request_id' => $requestId,
            'verified' => false,
            'verified_at' => null,
        ]);
        $package->save();
    }

    private function removePartialFile(string $path): void
    {
        if (is_file($path)) {
            @unlink($path);
        }
    }
}
