<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Exceptions\SupportCenter\SupportCenterException;
use App\Models\HostSystemAuthorization;
use App\Models\HostSystemUpdateCheck;
use Throwable;

/**
 * 官方系统更新检查：成功更新快照；失败仅记录检查结果，保留上次成功包信息。
 */
final class HostSystemUpdateCheckService
{
    public function __construct(
        private readonly SupportCenterOpenApiTransport $transport,
        private readonly SupportCenterRequestContextBuilder $contextBuilder,
        private readonly HostSystemUpdateCheckSnapshotMapper $snapshotMapper,
        private readonly HostSystemUpdateEntitlementGate $entitlementGate,
        private readonly HostSiteOnboardingStatusCacheStore $siteStatusCacheStore,
        private readonly HostSystemUpdateReleaseChannelResolver $releaseChannelResolver,
        private readonly HostSystemUpdatePackageTypeGuard $packageTypeGuard,
        private readonly HostSystemUpdateMinUpgradableVersionGuard $minUpgradableVersionGuard,
        private readonly SupportCenterAuditLogger $auditLogger,
        private readonly HostSystemUpdateNotificationSyncService $notificationSyncService,
    ) {}

    public function check(?string $releaseChannel = null): HostSystemUpdateCheck
    {
        if (! $this->entitlementGate->canCheckUpdate()) {
            throw SupportCenterException::entitlementBlocked(
                userFacingMessage: $this->entitlementGate->checkReason(),
                operation: 'host_system_update_check',
            );
        }

        $record = HostSystemUpdateCheck::singleton();
        $siteNo = $this->resolveSiteNo();
        $licenseNo = $this->resolveLicenseNo();
        $channel = $this->normalizeReleaseChannel($releaseChannel);
        $payload = $this->contextBuilder->systemUpdateCheckBody(
            releaseChannel: $channel,
            siteNo: $siteNo,
            licenseNo: $licenseNo,
        );

        $this->auditLogger->logSystemUpdateCheckStarted($siteNo, $licenseNo, $channel);

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

            $this->packageTypeGuard->assertCheckResponseAllowsInstall($responseData);
            $this->minUpgradableVersionGuard->assertMeetsMinimumForCheckResponse($responseData);

            $attributes = $this->snapshotMapper->mapResponseToAttributes($responseData, $channel);
            $attributes['package_type'] = $this->packageTypeGuard->resolveFromCheckResponse($responseData)
                ?? HostSystemUpdatePackageTypeGuard::INSTALL_PACKAGE_TYPE_FULL;

            $this->assertInstallableSnapshotHasPackageId($attributes, $responseData);

            $record->fill($attributes);
            $record->fill([
                'last_check_status' => 'success',
                'last_check_message' => null,
                'last_check_error_code' => null,
                'last_request_id' => $this->nullableString($responseData['request_id'] ?? null),
                'last_checked_at' => $this->snapshotMapper->parseCheckedAt($responseData['checked_at'] ?? null)
                    ?? now()->utc(),
            ]);
            $record->save();

            $this->auditLogger->logSystemUpdateCheckCompleted(
                siteNo: $siteNo,
                licenseNo: $licenseNo,
                updateAvailable: (bool) $record->update_available,
                latestVersion: $record->latest_version,
            );

            try {
                $this->notificationSyncService->sync($record->fresh() ?? $record);
            } catch (Throwable $exception) {
                report($exception);
            }

            return $record->fresh() ?? $record;
        } catch (SupportCenterException $exception) {
            $record->fill([
                'last_check_status' => 'failed',
                'last_check_message' => $exception->userFacingMessage(),
                'last_check_error_code' => $exception->errorCode(),
                'last_request_id' => $exception->requestId(),
                'last_checked_at' => now()->utc(),
            ]);
            $record->save();

            $this->auditLogger->logSystemUpdateCheckFailed($siteNo, $licenseNo, $exception);

            throw $exception;
        }
    }

    /**
     * @param  array<string, mixed>  $attributes
     * @param  array<string, mixed>  $responseData
     */
    private function assertInstallableSnapshotHasPackageId(array $attributes, array $responseData): void
    {
        if (! ($attributes['update_available'] ?? false)) {
            return;
        }

        $packageId = $attributes['package_id'] ?? null;

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

        throw new SupportCenterException(
            userFacingMessage: (string) __('dberp.system_update.package.package_id_missing'),
            errorCode: 'package_id_missing',
            requestId: $this->nullableString($responseData['request_id'] ?? null),
            operation: 'host_system_update_check',
        );
    }

    private function normalizeReleaseChannel(?string $releaseChannel): string
    {
        return $this->releaseChannelResolver->resolveOpenHostChannel($releaseChannel);
    }

    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 nullableString(mixed $value): ?string
    {
        if (! is_string($value)) {
            return null;
        }

        $trimmed = trim($value);

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