<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStatus;
use App\Enums\SupportCenter\HostSystemUpdateExecutionStep;
use App\Exceptions\SupportCenter\HostSystemUpdateExecutionException;
use App\Exceptions\SupportCenter\SupportCenterException;
use App\Models\HostSystemUpdateExecution;
use App\Models\HostSystemUpdatePackage;
use App\Support\DberpProductVersion;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

/**
 * 基于已校验且 manifest 已解析的更新包创建执行任务（不启动真实更新）。
 */
final class HostSystemUpdateExecutionCreateService
{
    public function __construct(
        private readonly HostSystemUpdateEntitlementGate $entitlementGate,
        private readonly HostSystemUpdateExecutionLock $executionLock,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly HostSystemUpdateExecutionManifestSummarizer $manifestSummarizer,
        private readonly SupportCenterAuditLogger $auditLogger,
        private readonly DberpProductVersion $productVersion,
        private readonly HostSystemUpdateMinUpgradableVersionGuard $minUpgradableVersionGuard,
    ) {}

    public function create(?HostSystemUpdatePackage $package = null): HostSystemUpdateExecution
    {
        if (! $this->entitlementGate->canExecuteUpdate()) {
            throw SupportCenterException::entitlementBlocked(
                $this->entitlementGate->executeReason(),
                'host_system_update_execution_create',
            );
        }

        $package ??= $this->resolveLatestReadyPackage();
        $manifest = $this->resolveParsedManifest($package);
        $this->minUpgradableVersionGuard->assertMeetsMinimumForExecution();
        $this->assertManifestForExecution($manifest);

        return DB::transaction(function () use ($package, $manifest): HostSystemUpdateExecution {
            $active = $this->executionLock->findActiveExecution();

            if ($active instanceof HostSystemUpdateExecution) {
                throw HostSystemUpdateExecutionException::activeExecutionExists((string) $active->execution_no);
            }

            $operator = Auth::user();
            $executionNo = $this->generateExecutionNo();

            $execution = new HostSystemUpdateExecution([
                'execution_no' => $executionNo,
                'package_id' => $package->package_id,
                'host_system_update_package_id' => $package->getKey(),
                'product_code' => (string) ($manifest[HostSystemUpdateManifestContract::PRODUCT_CODE] ?? 'dberp'),
                'from_version' => $manifest[HostSystemUpdateManifestContract::FROM_VERSION] ?? null,
                'from_update_version' => $manifest[HostSystemUpdateManifestContract::FROM_UPDATE_VERSION] ?? null,
                'to_version' => $manifest[HostSystemUpdateManifestContract::TO_VERSION] ?? null,
                'to_update_version' => $manifest[HostSystemUpdateManifestContract::TO_UPDATE_VERSION] ?? null,
                'release_channel' => $manifest[HostSystemUpdateManifestContract::RELEASE_CHANNEL]
                    ?? $package->release_channel,
                'package_type' => $manifest[HostSystemUpdateManifestContract::PACKAGE_TYPE]
                    ?? $package->package_type,
                'status' => HostSystemUpdateExecutionStatus::Pending->value,
                'current_step' => HostSystemUpdateExecutionStep::Created->value,
                'progress_percent' => HostSystemUpdateExecutionStateCatalog::progressForStep(
                    HostSystemUpdateExecutionStep::Created->value,
                ),
                'operator_id' => $operator instanceof Authenticatable ? $operator->getAuthIdentifier() : null,
                'operator_name' => $this->operatorName($operator),
                'raw_manifest' => $this->manifestSummarizer->summarize($manifest),
            ]);
            $execution->save();

            $execution = $this->executionLock->acquire($execution, $executionNo);

            $this->executionLogger->logStepStarted(
                $execution,
                HostSystemUpdateExecutionStep::Created->value,
                (string) __('dberp.system_update.execution.log.created'),
                [
                    'execution_no' => $executionNo,
                    'package_id' => $package->package_id,
                    'to_update_version' => $execution->to_update_version,
                ],
            );

            $this->auditLogger->logSystemUpdateExecutionCreated($execution);

            return $execution->fresh(['package']) ?? $execution;
        });
    }

    /**
     * @param  array<string, mixed>  $manifest
     */
    private function assertManifestForExecution(array $manifest): void
    {
        if (($manifest[HostSystemUpdateManifestContract::PRODUCT_CODE] ?? null) !== 'dberp') {
            throw HostSystemUpdateExecutionException::productCodeMismatch();
        }

        $current = $this->productVersion->updateVersion();
        $toUpdateVersion = $manifest[HostSystemUpdateManifestContract::TO_UPDATE_VERSION] ?? null;

        if (! is_int($toUpdateVersion) || $toUpdateVersion <= $current) {
            throw HostSystemUpdateExecutionException::toUpdateVersionNotGreater($current, $toUpdateVersion);
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function resolveParsedManifest(HostSystemUpdatePackage $package): array
    {
        if (! $package->verified || $package->download_status !== HostSystemUpdatePackage::STATUS_VERIFIED) {
            throw HostSystemUpdateExecutionException::packageNotVerified();
        }

        if ($package->manifest_status !== HostSystemUpdatePackage::MANIFEST_STATUS_VALID) {
            throw HostSystemUpdateExecutionException::manifestNotParsed();
        }

        $manifest = $package->manifest;

        if (! is_array($manifest) || $manifest === []) {
            throw HostSystemUpdateExecutionException::manifestNotParsed();
        }

        return $manifest;
    }

    private function resolveLatestReadyPackage(): HostSystemUpdatePackage
    {
        $package = HostSystemUpdatePackage::query()
            ->where('verified', true)
            ->where('download_status', HostSystemUpdatePackage::STATUS_VERIFIED)
            ->where('manifest_status', HostSystemUpdatePackage::MANIFEST_STATUS_VALID)
            ->orderByDesc('id')
            ->first();

        if ($package instanceof HostSystemUpdatePackage) {
            return $package;
        }

        throw HostSystemUpdateExecutionException::noVerifiedPackage();
    }

    private function generateExecutionNo(): string
    {
        return 'DBEU-'.now()->utc()->format('YmdHis').'-'.strtoupper(Str::random(6));
    }

    private function operatorName(?Authenticatable $operator): ?string
    {
        if ($operator === null) {
            return null;
        }

        if (method_exists($operator, 'getAttribute')) {
            $name = $operator->getAttribute('name');

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

        return null;
    }
}
