<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\SupportCenterException;
use App\Models\HostSystemAuthorization;
use App\Models\HostSystemUpdateCheck;
use App\Models\HostSystemUpdatePackage;
use DomainException;

/**
 * 向 Support Center 申请系统更新包下载令牌（令牌不落库）。
 */
final class HostSystemUpdateDownloadTokenService
{
    public function __construct(
        private readonly SupportCenterOpenApiTransport $transport,
        private readonly SupportCenterRequestContextBuilder $contextBuilder,
        private readonly HostSystemUpdatePackageMetadataMapper $metadataMapper,
        private readonly HostSystemUpdateEntitlementGate $entitlementGate,
        private readonly HostSiteOnboardingStatusCacheStore $siteStatusCacheStore,
        private readonly HostSystemUpdateReleaseChannelResolver $releaseChannelResolver,
        private readonly HostSystemUpdatePackageTypeGuard $packageTypeGuard,
        private readonly HostSystemUpdateMinUpgradableVersionGuard $minUpgradableVersionGuard,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function requestToken(?HostSystemUpdateCheck $checkSnapshot = null): HostSystemUpdateDownloadTokenResult
    {
        if (! $this->entitlementGate->canDownloadPackage()) {
            throw SupportCenterException::entitlementBlocked(
                userFacingMessage: $this->entitlementGate->downloadReason(),
                operation: 'host_system_update_download_token',
            );
        }

        $checkSnapshot ??= HostSystemUpdateCheck::singleton();
        $this->assertCheckSnapshotReady($checkSnapshot);
        $this->packageTypeGuard->assertInstallPackageType($checkSnapshot->package_type, 'check_snapshot');
        $this->minUpgradableVersionGuard->assertMeetsMinimum(
            $checkSnapshot->min_upgradable_version,
            is_array($checkSnapshot->requirements) ? $checkSnapshot->requirements : null,
        );

        $siteNo = $this->resolveSiteNo();
        $licenseNo = $this->resolveLicenseNo();
        $packageId = (string) $checkSnapshot->package_id;
        $releaseChannel = (string) ($checkSnapshot->release_channel
            ?? $this->releaseChannelResolver->resolveOpenHostChannel());

        $payload = $this->contextBuilder->systemUpdateDownloadTokenBody(
            packageId: $packageId,
            releaseChannel: $releaseChannel,
            siteNo: $siteNo,
            licenseNo: $licenseNo,
            currentVersion: $checkSnapshot->current_version,
            currentUpdateVersion: $this->resolveCurrentUpdateVersion($checkSnapshot),
        );

        $this->auditLogger->logSystemUpdatePackageDownloadTokenRequested(
            packageId: $packageId,
            version: $checkSnapshot->package_version,
            releaseChannel: $releaseChannel,
        );

        try {
            $responseData = $this->transport->post(
                SupportCenterOpenHostEndpoints::SYSTEM_UPDATE_DOWNLOAD_TOKEN,
                $payload,
                'host_system_update_download_token',
            );

            $downloadToken = $this->nullableString($responseData['download_token'] ?? null);

            if ($downloadToken === null) {
                throw SupportCenterException::invalidResponseFormat(operation: 'host_system_update_download_token');
            }

            $package = $this->persistPackageMetadata($responseData, $checkSnapshot, $responseData['request_id'] ?? null);

            return new HostSystemUpdateDownloadTokenResult(
                downloadToken: $downloadToken,
                downloadUrl: $this->nullableString($responseData['download_url'] ?? null),
                package: $package,
                requestId: $this->nullableString($responseData['request_id'] ?? null),
            );
        } catch (SupportCenterException $exception) {
            $this->auditLogger->logSystemUpdatePackageDownloadFailed(
                packageId: $packageId,
                version: $checkSnapshot->package_version,
                releaseChannel: $releaseChannel,
                exception: $exception,
            );

            throw $exception;
        }
    }

    private function assertCheckSnapshotReady(HostSystemUpdateCheck $checkSnapshot): void
    {
        if ($checkSnapshot->last_check_status !== 'success') {
            throw new DomainException((string) __('dberp.system_update.package.check_snapshot_not_ready'));
        }

        if (! $checkSnapshot->update_available) {
            throw new DomainException((string) __('dberp.system_update.package.no_update_available'));
        }

        if ($checkSnapshot->package_id === null || trim((string) $checkSnapshot->package_id) === '') {
            throw new DomainException((string) __('dberp.system_update.package.package_id_missing'));
        }
    }

    /**
     * @param  array<string, mixed>  $responseData
     */
    private function persistPackageMetadata(
        array $responseData,
        HostSystemUpdateCheck $checkSnapshot,
        mixed $requestId,
    ): HostSystemUpdatePackage {
        $attributes = $this->metadataMapper->mapTokenResponseToAttributes($responseData, $checkSnapshot);
        $packageId = $attributes['package_id'] ?? null;

        /** @var HostSystemUpdatePackage $package */
        $package = HostSystemUpdatePackage::query()->updateOrCreate(
            [
                'package_id' => $packageId,
                'version' => $attributes['version'] ?? null,
                'release_channel' => $attributes['release_channel'] ?? null,
            ],
            array_merge($attributes, [
                'download_status' => HostSystemUpdatePackage::STATUS_TOKEN_ISSUED,
                'download_message' => null,
                'download_error_code' => null,
                'download_request_id' => is_string($requestId) ? trim($requestId) : null,
                'verified' => false,
                'verified_at' => null,
            ]),
        );

        return $package->fresh() ?? $package;
    }

    private function resolveSiteNo(): ?string
    {
        $authorization = HostSystemAuthorization::singleton();
        $fromSnapshot = $this->nullableString($authorization->site_no);

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

        $configured = config('dberp.support_center.site_no');

        if (is_string($configured) && trim($configured) !== '') {
            return trim($configured);
        }

        $cached = $this->siteStatusCacheStore->get();
        $siteNo = is_array($cached) ? ($cached['site_no'] ?? null) : null;

        return is_string($siteNo) && trim($siteNo) !== '' ? trim($siteNo) : null;
    }

    private function resolveLicenseNo(): ?string
    {
        $authorization = HostSystemAuthorization::singleton();
        $fromSnapshot = $this->nullableString($authorization->license_no);

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

        $configured = config('dberp.support_center.license_no');

        if (is_string($configured) && trim($configured) !== '') {
            return trim($configured);
        }

        $cached = $this->siteStatusCacheStore->get();

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

        $data = $cached['data'] ?? null;

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

        $licenseNo = $data['license_no'] ?? null;

        return is_string($licenseNo) && trim($licenseNo) !== '' ? trim($licenseNo) : null;
    }

    private function resolveCurrentUpdateVersion(HostSystemUpdateCheck $checkSnapshot): string
    {
        $fromSnapshot = $checkSnapshot->current_update_version;

        if (is_int($fromSnapshot) && $fromSnapshot > 0) {
            return (string) $fromSnapshot;
        }

        return (string) config('dberp.version.update_version', 0);
    }

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

        $trimmed = trim($value);

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