<?php

declare(strict_types=1);

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,
    ) {}

    /**
     * @param  array<string, mixed>|null  $requirements
     */
    public function assertMeetsMinimum(?string $minDisplayVersion, ?array $requirements = null): void
    {
        $minUpdateVersion = $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
    {
        $requires = $manifest[HostSystemUpdateManifestContract::REQUIRES] ?? null;
        $requirements = is_array($requires) ? $requires : null;
        $minDisplay = $this->nullableString($manifest['min_upgradable_version'] ?? null)
            ?? $this->resolveMinDisplayVersion($requirements);

        try {
            $this->assertMeetsMinimum($minDisplay, $requirements);
        } 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
    {
        $check = HostSystemUpdateCheck::singleton();

        $this->assertMeetsMinimum(
            $this->nullableString($check->min_upgradable_version),
            is_array($check->requirements) ? $check->requirements : null,
        );
    }

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

        $minDisplay = $this->nullableString($package['min_version'] ?? null)
            ?? $this->resolveMinDisplayVersion($requirements);

        $this->assertMeetsMinimum($minDisplay, $requirements);
    }

    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;
    }
}
