<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStatus;
use App\Models\HostSystemUpdateExecution;
use App\Models\User;
use App\Services\DberpPermissionService;
use Illuminate\Contracts\Cache\Lock;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Log;
use Throwable;

/**
 * 零配置自动更新检查：仅在后台 ping 后于响应发送完毕后 opportunistic 触发，不阻塞页面渲染。
 */
final class HostSystemUpdateAutoCheckTriggerService
{
    public const SKIP_DISABLED = 'disabled';

    public const SKIP_NO_PERMISSION = 'no_permission';

    public const SKIP_THROTTLED = 'throttled';

    public const SKIP_LOCKED = 'locked';

    public const SKIP_UPDATE_RUNNING = 'update_running';

    public const SKIP_CONNECTIVITY = 'connectivity_blocked';

    public const SKIP_ENTITLEMENT = 'entitlement_blocked';

    public function __construct(
        private readonly HostSystemUpdateAutoCheckStateStore $stateStore,
        private readonly DberpPermissionService $permissionService,
        private readonly HostSystemUpdateEntitlementGate $entitlementGate,
        private readonly SupportCenterConnectivityGuard $connectivityGuard,
        private readonly HostSystemUpdateCheckService $checkService,
    ) {}

    /**
     * @return array{queued: bool, reason?: string}
     */
    public function handlePing(User $user): array
    {
        $skipReason = $this->evaluateSkipReason($user);

        if ($skipReason !== null) {
            return [
                'queued' => false,
                'reason' => $skipReason,
            ];
        }

        $lockSeconds = $this->lockSeconds();
        $lock = $this->stateStore->acquireLock($lockSeconds);

        if ($lock === null) {
            return [
                'queued' => false,
                'reason' => self::SKIP_LOCKED,
            ];
        }

        App::terminating(function () use ($lock): void {
            $this->runDeferredCheck($lock);
        });

        return [
            'queued' => true,
            'reason' => 'queued',
        ];
    }

    public function performImmediateCheck(bool $ignoreThrottle = false): bool
    {
        if (! $ignoreThrottle && $this->stateStore->isWithinIntervalHours($this->intervalHours())) {
            return false;
        }

        if ($this->isSystemUpdateRunning()) {
            return false;
        }

        if (! $this->connectivityGuard->canMakeRealCalls()) {
            return false;
        }

        if (! $this->entitlementGate->canCheckUpdate()) {
            return false;
        }

        $lock = $this->stateStore->acquireLock($this->lockSeconds());

        if ($lock === null) {
            return false;
        }

        try {
            $this->executeCheck();

            return true;
        } finally {
            $lock->release();
        }
    }

    private function runDeferredCheck(Lock $lock): void
    {
        try {
            if ($this->isSystemUpdateRunning()) {
                return;
            }

            if (! $this->connectivityGuard->canMakeRealCalls()) {
                return;
            }

            if (! $this->entitlementGate->canCheckUpdate()) {
                return;
            }

            $this->executeCheck();
        } catch (Throwable $exception) {
            $this->stateStore->recordAttempt();
            Log::warning('dberp.host_system_update.auto_check.failed', [
                'message' => $exception->getMessage(),
            ]);
        } finally {
            $lock->release();
        }
    }

    private function executeCheck(): void
    {
        $this->stateStore->recordAttempt();
        $this->checkService->check();
        $this->stateStore->recordSuccess();
    }

    private function evaluateSkipReason(User $user): ?string
    {
        if (! $this->isEnabled()) {
            return self::SKIP_DISABLED;
        }

        if (! $this->permissionService->allows($user, DberpPermissionService::VIEW_HOST_SYSTEM_UPDATE)) {
            return self::SKIP_NO_PERMISSION;
        }

        if ($this->stateStore->isWithinIntervalHours($this->intervalHours())) {
            return self::SKIP_THROTTLED;
        }

        if ($this->isSystemUpdateRunning()) {
            return self::SKIP_UPDATE_RUNNING;
        }

        if (! $this->connectivityGuard->canMakeRealCalls()) {
            return self::SKIP_CONNECTIVITY;
        }

        if (! $this->entitlementGate->canCheckUpdate()) {
            return self::SKIP_ENTITLEMENT;
        }

        return null;
    }

    private function isSystemUpdateRunning(): bool
    {
        return HostSystemUpdateExecution::query()
            ->whereIn('status', HostSystemUpdateExecutionStatus::activeValues())
            ->exists();
    }

    private function isEnabled(): bool
    {
        return (bool) config('dberp.system_update.auto_check.enabled', true);
    }

    private function intervalHours(): int
    {
        return max(1, (int) config('dberp.system_update.auto_check.interval_hours', 24));
    }

    private function lockSeconds(): int
    {
        return max(60, (int) config('dberp.system_update.auto_check.lock_seconds', 300));
    }
}
