<?php

declare(strict_types=1);

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

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateExecutionException;
use App\Exceptions\SupportCenter\HostSystemUpdateManifestException;
use App\Exceptions\SupportCenter\SupportCenterException;
use App\Models\HostSystemUpdateCheck;
use App\Support\DberpProductVersion;

/**
 * 校验当前实例是否达到官方公布的最低可在线升级版本。
 */
final class HostSystemUpdateMinUpgradableVersionGuard
{
    public function __construct(
        private readonly DberpProductVersion $productVersion,
        private readonly HostSystemUpdatePolicyResolver $policyResolver,
    ) {}

    /**
     * @param  array<string, mixed>|null  $requirements
     */
    public function assertMeetsMinimum(
        ?string $minDisplayVersion,
        ?array $requirements = null,
        ?int $explicitMinUpdateVersion = null,
    ): void {
        $minUpdateVersion = $explicitMinUpdateVersion ?? $this->resolveMinUpdateVersion($requirements);

        if ($minUpdateVersion !== null && $this->productVersion->updateVersion() < $minUpdateVersion) {
            throw $this->blockedException($minDisplayVersion, $minUpdateVersion);
        }

        $minimumDisplay = $minDisplayVersion ?? $this->resolveMinDisplayVersion($requirements);

        if ($minimumDisplay === null) {
            return;
        }

        if (! $this->displayVersionMeetsMinimum($minimumDisplay)) {
            throw $this->blockedException($minimumDisplay, $minUpdateVersion);
        }
    }

    /**
     * @param  array<string, mixed>|null  $requirements
     */
    public function resolveMinDisplayVersion(?array $requirements): ?string
    {
        if ($requirements !== null) {
            $fromRequirements = $this->nullableString($requirements['min_version'] ?? null);

            if ($fromRequirements !== null) {
                return $fromRequirements;
            }

            $fromUpdateDisplay = $this->nullableString($requirements['min_display_version'] ?? null);

            if ($fromUpdateDisplay !== null) {
                return $fromUpdateDisplay;
            }
        }

        return null;
    }

    /**
     * @param  array<string, mixed>|null  $requirements
     */
    public function resolveMinUpdateVersion(?array $requirements): ?int
    {
        if ($requirements === null) {
            return null;
        }

        $value = $requirements['min_update_version'] ?? null;

        if ($value === null || $value === '') {
            return null;
        }

        if (is_int($value)) {
            return $value;
        }

        if (is_numeric($value)) {
            return (int) $value;
        }

        return null;
    }

    public function assertMeetsMinimumForManifest(array $manifest): void
    {
        $policy = $this->policyResolver->resolve($manifest);
        $requires = $manifest[HostSystemUpdateManifestContract::REQUIRES] ?? null;
        $requirements = $this->policyResolver->canonicalRequirements(
            is_array($requires) ? $requires : null,
            $policy,
        );

        try {
            $this->assertMeetsMinimum($policy['min_version'], $requirements, $policy['min_update_version']);
        } catch (SupportCenterException $exception) {
            throw HostSystemUpdateManifestException::validationFailed(
                $exception->userFacingMessage(),
                'manifest_min_upgradable_version_unmet',
            );
        }
    }

    public function assertMeetsMinimumForExecution(): void
    {
        try {
            $this->assertMeetsMinimumForCheckRecord();
        } catch (SupportCenterException $exception) {
            throw HostSystemUpdateExecutionException::minUpgradableVersionUnmet($exception->userFacingMessage());
        }
    }

    public function assertMeetsMinimumForCheckRecord(): void
    {
        $this->assertMeetsMinimumForCheckSnapshot(HostSystemUpdateCheck::singleton());
    }

    public function assertMeetsMinimumForCheckSnapshot(HostSystemUpdateCheck $check): void
    {
        $this->assertMeetsMinimum(
            $this->nullableString($check->min_upgradable_version),
            is_array($check->requirements) ? $check->requirements : null,
            is_int($check->min_upgradable_update_version)
                ? $check->min_upgradable_update_version
                : null,
        );
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function assertMeetsMinimumForCheckResponse(array $data): void
    {
        $policy = $this->policyResolver->resolve($data);
        $package = is_array($data['package'] ?? null) ? $data['package'] : [];
        $requirements = is_array($package['requirements'] ?? null)
            ? $package['requirements']
            : (is_array($data['requirements'] ?? null) ? $data['requirements'] : null);
        $requirements = $this->policyResolver->canonicalRequirements($requirements, $policy);

        $this->assertMeetsMinimum($policy['min_version'], $requirements, $policy['min_update_version']);
    }

    private function displayVersionMeetsMinimum(string $minimumDisplay): bool
    {
        $current = ltrim($this->productVersion->displayVersion(), 'vV');
        $minimum = ltrim(trim($minimumDisplay), 'vV');

        if ($current === '' || $minimum === '') {
            return true;
        }

        return version_compare($current, $minimum, '>=');
    }

    private function blockedException(?string $minDisplay, ?int $minUpdateVersion): SupportCenterException
    {
        $currentDisplay = $this->productVersion->displayVersion();
        $currentUpdate = (string) $this->productVersion->updateVersion();

        return new SupportCenterException(
            userFacingMessage: (string) __('dberp.system_update.check.min_upgradable_version_unmet', [
                'current' => $currentDisplay,
                'current_update' => $currentUpdate,
                'minimum' => $minDisplay ?? (string) ($minUpdateVersion ?? ''),
            ]),
            errorCode: 'min_upgradable_version_unmet',
            operation: 'host_system_update_check',
        );
    }

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

        $trimmed = trim($value);

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