<?php

declare(strict_types=1);

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);
        $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,
            'min_upgradable_version' => $this->resolveMinUpgradableVersion($requirements, $package),
            '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;
    }

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

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

        return $this->nullableString($package['min_version'] ?? null);
    }

    /**
     * @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);
    }

    /**
     * @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;
        }
    }
}
