<?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\HostSystemAuthorization;
use App\Services\SupportCenter\SupportCenterOpenApiTransport;
use App\Services\SupportCenter\SupportCenterOpenHostEndpoints;
use App\Services\SupportCenter\SupportCenterRequestContextBuilder;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * 向 Support Center 检查已安装插件是否有新版本。
 */
final class HostPluginUpdateCheckService
{
    public function __construct(
        private readonly SupportCenterOpenApiTransport $transport,
        private readonly SupportCenterRequestContextBuilder $contextBuilder,
        private readonly HostPluginSupportCenterContextResolver $contextResolver,
        private readonly HostPluginEntitlementGate $entitlementGate,
        private readonly HostPluginOperationLogger $operationLogger,
    ) {}

    public function check(string $pluginCode, ?string $installedVersion = null): HostPluginUpdateCheckResult
    {
        $normalizedCode = trim($pluginCode);
        $normalizedInstalled = is_string($installedVersion) ? trim($installedVersion) : '';

        if ($normalizedCode === '') {
            return new HostPluginUpdateCheckResult(
                state: HostPluginUpdateCheckResult::STATE_CHECK_UNAVAILABLE,
                installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
            );
        }

        $localState = $this->resolveLocalBlockingState();

        if ($localState !== null) {
            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
                $normalizedCode,
                'local_state='.$localState,
                ['installed_version' => $normalizedInstalled],
            );

            return new HostPluginUpdateCheckResult(
                state: $localState,
                installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
                guidance: $this->guidanceForState($localState),
            );
        }

        $this->operationLogger->logStarted(
            HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
            $normalizedCode,
            ['installed_version' => $normalizedInstalled],
        );

        try {
            $response = $this->transport->post(
                SupportCenterOpenHostEndpoints::PLUGIN_UPDATE_CHECK,
                $this->contextBuilder->pluginUpdateCheckBody(
                    pluginCode: $normalizedCode,
                    installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
                    siteNo: $this->contextResolver->resolveSiteNo(),
                    licenseNo: $this->contextResolver->resolveLicenseNo(),
                ),
                'host_plugin_update_check',
            );

            $latestVersion = $this->nullableString($response['latest_version'] ?? $response['version'] ?? null);
            $updateAvailable = filter_var(
                $response['update_available'] ?? $response['has_update'] ?? false,
                FILTER_VALIDATE_BOOLEAN,
            );

            if ($latestVersion !== null
                && $normalizedInstalled !== ''
                && version_compare($latestVersion, $normalizedInstalled, '<=')) {
                $updateAvailable = false;
            }

            $remoteLicenseState = $this->mapRemoteLicenseState($response);

            if ($remoteLicenseState !== null) {
                $this->operationLogger->logCompleted(
                    HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
                    $normalizedCode,
                    'remote_license_state='.$remoteLicenseState,
                );

                return new HostPluginUpdateCheckResult(
                    state: $remoteLicenseState,
                    latestVersion: $latestVersion,
                    installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
                    guidance: $this->guidanceForState($remoteLicenseState),
                );
            }

            $state = $updateAvailable
                ? HostPluginUpdateCheckResult::STATE_UPDATE_AVAILABLE
                : HostPluginUpdateCheckResult::STATE_UP_TO_DATE;

            $this->operationLogger->logCompleted(
                HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
                $normalizedCode,
                'state='.$state,
                ['latest_version' => $latestVersion],
            );

            return new HostPluginUpdateCheckResult(
                state: $state,
                latestVersion: $latestVersion,
                installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
            );
        } catch (SupportCenterException $exception) {
            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
                $normalizedCode,
                (string) __('dberp.plugin_center.update.check_unavailable'),
                $exception->errorCode(),
                $exception->requestId(),
            );

            return new HostPluginUpdateCheckResult(
                state: HostPluginUpdateCheckResult::STATE_CHECK_UNAVAILABLE,
                installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
                guidance: (string) __('dberp.plugin_center.update.check_unavailable'),
            );
        } catch (Throwable $exception) {
            Log::warning('dberp.plugin_center.update_check_failed', [
                'plugin_code' => $normalizedCode,
                'exception_class' => $exception::class,
            ]);

            $this->operationLogger->logFailed(
                HostPluginOperationLogger::OPERATION_UPDATE_CHECK,
                $normalizedCode,
                (string) __('dberp.plugin_center.update.check_unavailable'),
                'update_check_failed',
                exception: $exception,
            );

            return new HostPluginUpdateCheckResult(
                state: HostPluginUpdateCheckResult::STATE_CHECK_UNAVAILABLE,
                installedVersion: $normalizedInstalled !== '' ? $normalizedInstalled : null,
                guidance: (string) __('dberp.plugin_center.update.check_unavailable'),
            );
        }
    }

    private function resolveLocalBlockingState(): ?string
    {
        $authorization = HostSystemAuthorization::singleton();

        if ($this->entitlementGate->connectivityBlocksRemoteOperations()) {
            return HostPluginUpdateCheckResult::STATE_CHECK_UNAVAILABLE;
        }

        if (! $this->entitlementGate->hasSiteBinding()) {
            return HostPluginUpdateCheckResult::STATE_CHECK_UNAVAILABLE;
        }

        if (! $authorization->authorized) {
            return HostPluginUpdateCheckResult::STATE_LICENSE_REQUIRED;
        }

        if ($this->entitlementGate->isServiceExpired()) {
            return HostPluginUpdateCheckResult::STATE_LICENSE_EXPIRED;
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $response
     */
    private function mapRemoteLicenseState(array $response): ?string
    {
        $reasonCode = strtolower(trim((string) ($response['reason_code'] ?? $response['update_status'] ?? '')));

        return match ($reasonCode) {
            'license_required', 'authorization_required', 'commercial_required' => HostPluginUpdateCheckResult::STATE_LICENSE_REQUIRED,
            'service_expired', 'license_expired', 'expired' => HostPluginUpdateCheckResult::STATE_LICENSE_EXPIRED,
            default => null,
        };
    }

    private function guidanceForState(string $state): ?string
    {
        $key = 'dberp.plugin_center.update.guidance.'.$state;

        return __($key) !== $key ? (string) __($key) : null;
    }

    private function nullableString(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $trimmed = trim($value);

        return $trimmed === '' ? null : $trimmed;
    }
}
