<?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\Models\HostSystemUpdateExecution;
use Illuminate\Support\Facades\DB;

/**
 * 系统更新执行任务状态机（仅记录状态与日志，不执行真实步骤）。
 */
final class HostSystemUpdateExecutionStateMachine
{
    public function __construct(
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly HostSystemUpdateExecutionLock $executionLock,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function transition(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateExecutionStatus $status,
        ?HostSystemUpdateExecutionStep $step = null,
        ?string $message = null,
        ?string $errorCode = null,
        ?string $errorMessage = null,
        ?array $context = null,
    ): HostSystemUpdateExecution {
        return DB::transaction(function () use (
            $execution,
            $status,
            $step,
            $message,
            $errorCode,
            $errorMessage,
            $context,
        ): HostSystemUpdateExecution {
            $execution->refresh();

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

            if ($fromStatus !== '' && ! HostSystemUpdateExecutionStateCatalog::canTransition($fromStatus, $toStatus)) {
                throw HostSystemUpdateExecutionException::invalidTransition($fromStatus, $toStatus);
            }

            $resolvedStep = $step?->value
                ?? HostSystemUpdateExecutionStateCatalog::defaultStepForStatus($toStatus)
                ?? (string) $execution->current_step;

            $now = now()->utc();
            $updates = [
                'status' => $toStatus,
                'current_step' => $resolvedStep,
                'progress_percent' => HostSystemUpdateExecutionStateCatalog::progressForStep($resolvedStep),
            ];

            if ($toStatus === HostSystemUpdateExecutionStatus::Running->value && $execution->started_at === null) {
                $updates['started_at'] = $now;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::Failed->value) {
                $updates['failed_at'] = $now;
                $updates['error_code'] = $errorCode;
                $updates['error_message'] = $errorMessage ?? $message;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::RollbackRequired->value) {
                $updates['error_code'] = $errorCode;
                $updates['error_message'] = $errorMessage ?? $message;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::RollbackFailed->value) {
                $updates['error_code'] = $errorCode;
                $updates['error_message'] = $errorMessage ?? $message;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::Cancelled->value) {
                $updates['cancelled_at'] = $now;
                $updates['error_message'] = $errorMessage ?? $message;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::Completed->value) {
                $updates['finished_at'] = $now;
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::RollingBack->value && $execution->rollback_started_at === null) {
                $updates['rollback_started_at'] = $now;
                $updates['rollback_status'] = HostSystemUpdateExecutionStatus::RollingBack->value;
            }

            if (in_array($toStatus, [
                HostSystemUpdateExecutionStatus::RolledBack->value,
                HostSystemUpdateExecutionStatus::RollbackFailed->value,
            ], true)) {
                $updates['rollback_finished_at'] = $now;
                $updates['rollback_status'] = $toStatus;
            }

            $execution->fill($updates);
            $execution->save();

            if ($status->isTerminal() && in_array($toStatus, [
                HostSystemUpdateExecutionStatus::Completed->value,
                HostSystemUpdateExecutionStatus::Failed->value,
                HostSystemUpdateExecutionStatus::Cancelled->value,
                HostSystemUpdateExecutionStatus::RolledBack->value,
                HostSystemUpdateExecutionStatus::RollbackFailed->value,
            ], true)) {
                $execution = $this->executionLock->release($execution);
            }

            $transitionMessage = $message ?? (string) __('dberp.system_update.execution.log.state_changed', [
                'from' => $fromStatus,
                'to' => $toStatus,
                'step' => $resolvedStep,
            ]);

            $this->executionLogger->logTransition(
                $execution,
                $resolvedStep,
                $transitionMessage,
                array_merge($context ?? [], [
                    'from_status' => $fromStatus,
                    'to_status' => $toStatus,
                ]),
            );

            $this->auditLogger->logSystemUpdateExecutionStateChanged($execution, $fromStatus);

            if ($toStatus === HostSystemUpdateExecutionStatus::Failed->value) {
                $this->auditLogger->logSystemUpdateExecutionFailed(
                    $execution,
                    $errorCode ?? 'execution_failed',
                    $errorMessage ?? $transitionMessage,
                );
            }

            if ($toStatus === HostSystemUpdateExecutionStatus::Cancelled->value) {
                $this->auditLogger->logSystemUpdateExecutionCancelled(
                    $execution,
                    $errorMessage ?? $transitionMessage,
                );
            }

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

    public function advanceStep(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateExecutionStep $step,
        ?string $message = null,
        ?array $context = null,
        ?int $progressPercent = null,
    ): HostSystemUpdateExecution {
        $execution->fill([
            'current_step' => $step->value,
            'progress_percent' => $progressPercent ?? HostSystemUpdateExecutionStateCatalog::progressForStep($step->value),
        ]);
        $execution->save();

        $this->executionLogger->logTransition(
            $execution,
            $step->value,
            $message ?? (string) __('dberp.system_update.execution.log.step_advanced', ['step' => $step->value]),
            $context,
        );

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

    public function cancel(
        HostSystemUpdateExecution $execution,
        ?string $reason = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::Cancelled,
            step: HostSystemUpdateExecutionStep::Failed,
            message: $reason ?? (string) __('dberp.system_update.execution.log.cancelled'),
            errorMessage: $reason,
        );
    }

    public function fail(
        HostSystemUpdateExecution $execution,
        string $errorCode,
        string $errorMessage,
        ?array $context = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::Failed,
            step: HostSystemUpdateExecutionStep::Failed,
            message: $errorMessage,
            errorCode: $errorCode,
            errorMessage: $errorMessage,
            context: $context,
        );
    }

    public function requireRollback(
        HostSystemUpdateExecution $execution,
        string $errorCode,
        string $errorMessage,
        ?array $context = null,
        ?HostSystemUpdateExecutionStep $step = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::RollbackRequired,
            step: $step ?? HostSystemUpdateExecutionStep::ReplaceFiles,
            message: $errorMessage,
            errorCode: $errorCode,
            errorMessage: $errorMessage,
            context: $context,
        );
    }

    public function startRollback(
        HostSystemUpdateExecution $execution,
        ?string $message = null,
        ?array $context = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::RollingBack,
            step: HostSystemUpdateExecutionStep::Rollback,
            message: $message ?? (string) __('dberp.system_update.recovery.log.rollback_started'),
            context: $context,
        );
    }

    public function completeRollback(
        HostSystemUpdateExecution $execution,
        ?string $message = null,
        ?array $context = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::RolledBack,
            step: HostSystemUpdateExecutionStep::Rollback,
            message: $message ?? (string) __('dberp.system_update.recovery.log.rollback_completed'),
            context: $context,
        );
    }

    public function failRollback(
        HostSystemUpdateExecution $execution,
        string $errorCode,
        string $errorMessage,
        ?array $context = null,
    ): HostSystemUpdateExecution {
        return $this->transition(
            execution: $execution,
            status: HostSystemUpdateExecutionStatus::RollbackFailed,
            step: HostSystemUpdateExecutionStep::Rollback,
            message: $errorMessage,
            errorCode: $errorCode,
            errorMessage: $errorMessage,
            context: $context,
        );
    }
}
