<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\HostSystemUpdateManifestException;
use App\Exceptions\SupportCenter\SupportCenterException;

/**
 * 强制仅接受最新全量升级包（package_type=full），拒绝 patch/增量/未知类型。
 */
final class HostSystemUpdatePackageTypeGuard
{
    public const INSTALL_PACKAGE_TYPE_FULL = 'full';

    /**
     * @var list<string>
     */
    private const REJECTED_INSTALL_TYPES = [
        'patch',
        'incremental',
        'delta',
        'partial',
        'hotfix',
        'diff',
        'system',
    ];

    /**
     * @param  array<string, mixed>  $data  Support Center check 响应 data 节点
     */
    public function resolveFromCheckResponse(array $data): ?string
    {
        /** @var array<string, mixed> $package */
        $package = is_array($data['package'] ?? null) ? $data['package'] : [];

        foreach ([
            'install_package_type',
            'upgrade_package_type',
            'package_type',
        ] as $key) {
            $fromPackage = $this->normalizeType($package[$key] ?? null);

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

            $fromRoot = $this->normalizeType($data[$key] ?? null);

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

        return null;
    }

    /**
     * @param  array<string, mixed>  $data
     */
    public function assertCheckResponseAllowsInstall(array $data): void
    {
        if (! $this->responseIndicatesInstallablePackage($data)) {
            return;
        }

        $type = $this->resolveFromCheckResponse($data);

        $this->assertInstallPackageType($type, 'check_response');
    }

    public function assertInstallPackageType(?string $packageType, string $context): void
    {
        $normalized = $this->normalizeType($packageType);

        if ($normalized === self::INSTALL_PACKAGE_TYPE_FULL) {
            return;
        }

        if ($normalized !== null && in_array($normalized, self::REJECTED_INSTALL_TYPES, true)) {
            throw $this->exceptionForRejectedType($normalized, $context);
        }

        throw $this->exceptionForRejectedType($normalized ?? 'unknown', $context);
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function responseIndicatesInstallablePackage(array $data): bool
    {
        $hasUpdate = filter_var($data['has_update'] ?? $data['update_available'] ?? false, FILTER_VALIDATE_BOOLEAN);

        if (! $hasUpdate) {
            return false;
        }

        /** @var array<string, mixed> $package */
        $package = is_array($data['package'] ?? null) ? $data['package'] : [];
        $packageId = $package['package_id'] ?? null;

        return is_string($packageId) && trim($packageId) !== '';
    }

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

        $trimmed = strtolower(trim($value));

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

    private function exceptionForRejectedType(string $type, string $context): SupportCenterException|HostSystemUpdateManifestException
    {
        $label = $type === '' ? 'unknown' : $type;

        if ($context === 'manifest') {
            throw HostSystemUpdateManifestException::validationFailed(
                (string) __('dberp.system_update.manifest.package_type_not_full', ['type' => $label]),
                'manifest_package_type_not_full',
            );
        }

        $operation = match ($context) {
            'check_snapshot', 'download_token' => 'host_system_update_download_token',
            default => 'host_system_update_check',
        };

        throw new SupportCenterException(
            userFacingMessage: (string) __('dberp.system_update.check.package_type_not_full', ['type' => $label]),
            errorCode: 'package_type_not_full',
            operation: $operation,
        );
    }
}
