<?php

declare(strict_types=1);

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

namespace App\Services\SupportCenter;

use App\Support\DberpProductVersion;
use Illuminate\Support\Carbon;

/**
 * Support Center 系统更新检查响应 → 本地快照字段映射。
 *
 * NOTE: Support Center Phase 1 不提供 manifest API；manifest.json 仅在后续下载 ZIP 后于 DBErp 侧解析。
 * 有可用更新时 package_type 须为 full（全量升级包）；patch/增量类型在检查阶段即拒绝。
 */
final class HostSystemUpdateCheckSnapshotMapper
{
    public function __construct(
        private readonly DberpProductVersion $productVersion,
    ) {}

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

        $requirements = $this->normalizeRequirements($package['requirements'] ?? $data['requirements'] ?? null);
        [$policy, $requirements] = $this->resolvePolicyWithMixedStateFallback($data, $requirements);
        $requirements = $this->mergeRiskMetadata($requirements, $package, $data);

        return [
            'product_code' => $this->nullableString($data['product_code'] ?? null) ?? 'dberp',
            'current_version' => $this->nullableString($data['current_version'] ?? null)
                ?? $this->productVersion->displayVersion(),
            'current_update_version' => $this->nullableInt($data['current_update_version'] ?? null)
                ?? $this->productVersion->updateVersion(),
            'latest_version' => $this->nullableString($data['latest_version'] ?? null),
            'latest_update_version' => $this->nullableInt($data['latest_update_version'] ?? null),
            'release_channel' => $this->nullableString($data['release_channel'] ?? null)
                ?? $this->nullableString($package['release_channel'] ?? null)
                ?? $releaseChannel,
            'update_available' => $this->resolveUpdateAvailable($data),
            'package_id' => $this->resolvePackageIdentifier($package, $data),
            'package_type' => $this->nullableString($package['package_type'] ?? null),
            'package_version' => $this->nullableString($package['version'] ?? null),
            'package_size' => $this->nullableInt($package['package_size'] ?? null),
            'checksum' => $this->nullableString($package['checksum'] ?? null),
            'checksum_algorithm' => $this->nullableString($package['checksum_algorithm'] ?? null),
            'signature' => $this->nullableString($package['signature'] ?? null),
            'release_notes' => $this->normalizeReleaseNotes($package['release_notes'] ?? null),
            'requirements' => $requirements,
            'force_update' => $policy['force_update'],
            'min_upgradable_version' => $policy['min_version'],
            'min_upgradable_update_version' => $policy['min_update_version'],
            'entitled' => $this->nullableBool($data['entitled'] ?? null),
            'entitlement_message' => $this->nullableString($data['entitlement_message'] ?? null),
            'raw_snapshot' => SupportCenterSnapshotSanitizer::sanitize($data),
        ];
    }

    /**
     * @param  array<string, mixed>  $data
     */
    private function resolveUpdateAvailable(array $data): bool
    {
        if (array_key_exists('has_update', $data)) {
            return filter_var($data['has_update'], FILTER_VALIDATE_BOOLEAN);
        }

        if (array_key_exists('update_available', $data)) {
            return filter_var($data['update_available'], FILTER_VALIDATE_BOOLEAN);
        }

        return false;
    }

    /**
     * @return array<string, mixed>|null
     */
    private function normalizeRequirements(mixed $value): ?array
    {
        if ($value === null) {
            return null;
        }

        if (! is_array($value)) {
            return null;
        }

        return $value === [] ? null : $value;
    }

    /**
     * @return array<string, mixed>|list<mixed>|null
     */
    private function normalizeReleaseNotes(mixed $value): ?array
    {
        if ($value === null) {
            return null;
        }

        if (is_array($value)) {
            return $value === [] ? null : $value;
        }

        if (is_string($value) && trim($value) !== '') {
            return ['text' => trim($value)];
        }

        return null;
    }

    /**
     * @param  array<string, mixed>  $package
     * @param  array<string, mixed>  $data
     */
    private function resolvePackageIdentifier(array $package, array $data): ?string
    {
        foreach ([
            $package['package_id'] ?? null,
            $package['id'] ?? null,
            $data['package_id'] ?? null,
        ] as $candidate) {
            $identifier = $this->stringifyIdentifier($candidate);

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

        return null;
    }

    private function stringifyIdentifier(mixed $value): ?string
    {
        if ($value === null || $value === '') {
            return null;
        }

        if (is_string($value) || is_int($value)) {
            $string = trim((string) $value);

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

        return null;
    }

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

        $trimmed = trim($value);

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

    private function nullableInt(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

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

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

        return null;
    }

    private function nullableBool(mixed $value): ?bool
    {
        if ($value === null) {
            return null;
        }

        return filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
    }

    /**
     * 分批替换期间 Mapper 可能先于完整策略解析器落盘；此窗口仍须识别 Support Center 顶层字段。
     *
     * @param  array<string, mixed>  $data
     * @param  array<string, mixed>|null  $requirements
     * @return array{0: array{force_update: bool, min_version: ?string, min_update_version: ?int}, 1: array<string, mixed>|null}
     */
    private function resolvePolicyWithMixedStateFallback(array $data, ?array $requirements): array
    {
        if (class_exists(HostSystemUpdatePolicyResolver::class)) {
            $resolver = new HostSystemUpdatePolicyResolver;
            $policy = $resolver->resolve($data);

            return [$policy, $resolver->canonicalRequirements($requirements, $policy)];
        }

        $package = is_array($data['package'] ?? null) ? $data['package'] : [];
        $packageRequirements = is_array($package['requirements'] ?? null) ? $package['requirements'] : [];
        $policy = [
            'force_update' => $this->nullableBool($data['force_update'] ?? $package['force_update'] ?? null) ?? false,
            'min_version' => $this->nullableString($data['min_upgradable_version'] ?? null)
                ?? $this->nullableString($package['min_upgradable_version'] ?? null)
                ?? $this->nullableString($packageRequirements['min_version'] ?? null),
            'min_update_version' => $this->nullableInt($data['min_upgradable_update_version'] ?? null)
                ?? $this->nullableInt($package['min_upgradable_update_version'] ?? null)
                ?? $this->nullableInt($packageRequirements['min_update_version'] ?? null),
        ];
        $canonical = $requirements ?? [];

        if ($policy['min_version'] !== null) {
            $canonical['min_version'] = $policy['min_version'];
        }

        if ($policy['min_update_version'] !== null) {
            $canonical['min_update_version'] = $policy['min_update_version'];
        }

        return [$policy, $canonical === [] ? null : $canonical];
    }

    /**
     * @param  array<string, mixed>|null  $requirements
     * @param  array<string, mixed>  $package
     * @param  array<string, mixed>  $data
     * @return array<string, mixed>|null
     */
    private function mergeRiskMetadata(?array $requirements, array $package, array $data): ?array
    {
        $riskLevel = $this->nullableString($package['update_risk_level'] ?? null)
            ?? $this->nullableString($package['risk_level'] ?? null)
            ?? $this->nullableString($data['update_risk_level'] ?? null)
            ?? $this->nullableString($data['risk_level'] ?? null);

        $riskSummary = $this->nullableString($package['update_risk_summary'] ?? null)
            ?? $this->nullableString($package['risk_summary'] ?? null)
            ?? $this->nullableString($data['update_risk_summary'] ?? null)
            ?? $this->nullableString($data['risk_summary'] ?? null);

        if ($riskLevel === null && $riskSummary === null) {
            return $requirements;
        }

        $merged = $requirements ?? [];

        if ($riskLevel !== null) {
            $merged['update_risk_level'] = $riskLevel;
        }

        if ($riskSummary !== null) {
            $merged['update_risk_summary'] = $riskSummary;
        }

        return $merged === [] ? null : $merged;
    }

    public function parseCheckedAt(mixed $value): ?Carbon
    {
        if (! is_string($value) || trim($value) === '') {
            return null;
        }

        try {
            return Carbon::parse($value)->utc();
        } catch (\Throwable) {
            return null;
        }
    }
}
