<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStatus;
use App\Enums\SupportCenter\HostSystemUpdateStepPhase;
use App\Exceptions\SupportCenter\HostSystemUpdateExecutionException;
use App\Models\HostSystemUpdateExecution;

/**
 * 在 CLI 中循环推进 {@see HostSystemUpdateStepRunner}，直至执行任务到达终态。
 */
class HostSystemUpdateCliRunnerService
{
    public function __construct(
        private readonly HostSystemUpdateCliExecutionPolicy $executionPolicy,
        private readonly HostSystemUpdateExecutionResumePhaseResolver $resumePhaseResolver,
        private readonly HostSystemUpdateStepProgressStore $progressStore,
        private readonly HostSystemUpdateStepRunner $stepRunner,
        private readonly HostSystemUpdateExecutionLock $executionLock,
    ) {}

    /**
     * 从检查官方版本开始执行完整系统更新（检查 → 下载 → 创建任务 → 安装），与 Web「立即更新」共用分步链。
     *
     * @param  callable(HostSystemUpdateStepResult): void|null  $onStep
     */
    public function runFull(?callable $onStep = null): HostSystemUpdateCliRunResult
    {
        try {
            $sessionId = $this->stepRunner->beginSession();
        } catch (HostSystemUpdateExecutionException $exception) {
            if ($exception->errorCode() === 'execution_active_exists') {
                throw HostSystemUpdateCliRunException::anotherExecutionActive(
                    $this->extractExecutionNoFromExceptionMessage($exception->userFacingMessage()),
                );
            }

            throw $exception;
        }

        while (true) {
            $result = $this->stepRunner->runNextStep($sessionId);

            if ($onStep !== null) {
                $onStep($result);
            }

            if ($result->terminal) {
                return $this->buildTerminalResult($result);
            }
        }
    }

    /**
     * @param  callable(HostSystemUpdateStepResult): void|null  $onStep
     */
    public function run(string $executionNo, ?callable $onStep = null): HostSystemUpdateCliRunResult
    {
        $execution = $this->findExecutionOrFail($executionNo);
        $this->executionPolicy->assertRunnable($execution);

        $execution = $execution->fresh(['package', 'backups']) ?? $execution;

        if ($execution->locked_at === null) {
            $execution = $this->executionLock->acquire($execution, $this->lockOwner());
        }

        $resume = $this->resumePhaseResolver->resolve($execution);

        $sessionId = $this->progressStore->createSessionForExecution(
            executionNo: (string) $execution->execution_no,
            phase: $resume->phase,
            migrationProgress: $resume->migrationProgress,
            migratePhaseComplete: $resume->migratePhaseComplete,
            batchProgress: $resume->batchProgress,
        );

        while (true) {
            $result = $this->stepRunner->runNextStep($sessionId);

            if ($onStep !== null) {
                $onStep($result);
            }

            if ($result->terminal) {
                return $this->buildTerminalResult($result);
            }
        }
    }

    public function findExecutionOrFail(string $executionNo): HostSystemUpdateExecution
    {
        $normalized = trim($executionNo);

        $execution = HostSystemUpdateExecution::query()
            ->with(['package'])
            ->where('execution_no', $normalized)
            ->first();

        if (! $execution instanceof HostSystemUpdateExecution) {
            throw HostSystemUpdateCliRunException::executionNotFound($normalized);
        }

        return $execution;
    }

    private function buildTerminalResult(HostSystemUpdateStepResult $result): HostSystemUpdateCliRunResult
    {
        $execution = $result->execution?->fresh(['package', 'backups', 'logs']) ?? $result->execution;

        if ($result->failed) {
            return HostSystemUpdateCliRunResult::failure(
                message: (string) ($result->userMessage
                    ?? __('dberp.system_update_center.cli.errors.step_failed')),
                execution: $execution,
                recoverySuggestion: $this->recoverySuggestionFor($execution),
            );
        }

        $status = (string) ($execution?->status ?? '');

        if ($status !== HostSystemUpdateExecutionStatus::Completed->value) {
            return HostSystemUpdateCliRunResult::failure(
                message: (string) __('dberp.system_update_center.cli.errors.unexpected_terminal_status', [
                    'status' => $this->statusLabel($status),
                ]),
                execution: $execution,
                recoverySuggestion: $this->recoverySuggestionFor($execution),
            );
        }

        return HostSystemUpdateCliRunResult::success(
            execution: $execution ?? $this->placeholderExecution(),
            message: (string) __('dberp.system_update_center.cli.success.completed', [
                'execution_no' => (string) ($execution?->execution_no ?? ''),
            ]),
        );
    }

    private function recoverySuggestionFor(?HostSystemUpdateExecution $execution): ?string
    {
        if (! $execution instanceof HostSystemUpdateExecution) {
            return (string) __('dberp.system_update_center.cli.suggestions.generic_failure');
        }

        $status = (string) ($execution->status ?? '');

        if ($status === HostSystemUpdateExecutionStatus::RollbackRequired->value) {
            return (string) __('dberp.system_update_center.workflow.recovery_hint');
        }

        if (is_string($execution->error_message) && trim($execution->error_message) !== '') {
            return (string) __('dberp.system_update_center.cli.suggestions.check_execution_logs');
        }

        return (string) __('dberp.system_update_center.cli.suggestions.generic_failure');
    }

    private function statusLabel(string $status): string
    {
        return (string) __('dberp.system_update_center.status.'.($status !== '' ? $status : 'unknown'));
    }

    private function lockOwner(): string
    {
        return 'cli:'.(string) getmypid();
    }

    private function placeholderExecution(): HostSystemUpdateExecution
    {
        return new HostSystemUpdateExecution;
    }

    public function phaseLabel(HostSystemUpdateStepPhase $phase): string
    {
        return (string) __('dberp.system_update_center.step_phases.'.$phase->value);
    }

    private function extractExecutionNoFromExceptionMessage(string $message): string
    {
        if (preg_match('/DBEU-[A-Z0-9-]+/i', $message, $matches) === 1) {
            return (string) $matches[0];
        }

        return $message;
    }
}
