<?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\HostSystemUpdateFileExecutionException;
use App\Exceptions\SupportCenter\HostSystemUpdatePostApplyException;
use App\Exceptions\SupportCenter\HostSystemUpdateRecoveryException;
use App\Exceptions\SupportCenter\HostSystemUpdateStagingException;
use App\Models\HostSystemUpdateExecution;
use Throwable;

/**
 * Phase 1 更新执行编排：维护模式 → 准备 → 应用 → 收尾；失败时交由 FailureHandler。
 *
 * NOTE: 当前为同步 Web 请求内执行，未接入队列；长时间更新可能受 PHP 超时限制。
 */
final class HostSystemUpdateExecutionOrchestrator
{
    public function __construct(
        private readonly HostSystemUpdateEntitlementGate $entitlementGate,
        private readonly HostSystemUpdateMaintenanceModeService $maintenanceModeService,
        private readonly HostSystemUpdatePrepareService $prepareService,
        private readonly HostSystemUpdateApplyService $applyService,
        private readonly HostSystemUpdateFinalizeService $finalizeService,
        private readonly HostSystemUpdateFailureHandler $failureHandler,
        private readonly HostSystemUpdateResultReporter $resultReporter,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function run(HostSystemUpdateExecution $execution): HostSystemUpdateExecution
    {
        if (! $this->entitlementGate->canExecuteUpdate()) {
            throw HostSystemUpdateExecutionException::entitlementBlocked(
                $this->entitlementGate->executeReason(),
            );
        }

        $this->assertRunnable($execution);

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

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

        $maintenanceEntered = false;

        try {
            $this->maintenanceModeService->enterMaintenance($execution);
            $maintenanceEntered = true;

            $execution = $this->prepareService->prepareStagingAndBackup($execution->fresh(['package', 'backups']) ?? $execution);
            $execution = $this->applyService->applyStagedChanges($execution->fresh(['package', 'backups']) ?? $execution);
            $execution = $this->finalizeService->finalizeApplyPhases($execution->fresh(['package', 'backups']) ?? $execution);

            $this->maintenanceModeService->attemptLeaveMaintenance($execution->fresh() ?? $execution);

            $completed = $this->finalizeService->markExecutionCompleted(
                $execution->fresh(['package', 'backups', 'logs']) ?? $execution,
            );
            $this->resultReporter->reportSuccess($completed);

            return $completed;
        } catch (Throwable $throwable) {
            $fresh = $execution->fresh(['package', 'backups', 'logs']) ?? $execution;

            $handled = $this->failureHandler->handle(
                $fresh,
                $this->buildFailureContext($fresh, $previousStatus, $throwable, $maintenanceEntered),
            );

            $this->resultReporter->reportFailure(
                $handled,
                $this->resolveReportErrorCode($throwable, $handled),
                $this->resolveReportUserMessage($throwable, $handled),
                $handled->current_step,
            );

            return $handled;
        }
    }

    private function assertRunnable(HostSystemUpdateExecution $execution): void
    {
        $status = (string) ($execution->status ?? '');

        if ($status !== HostSystemUpdateExecutionStatus::Pending->value) {
            throw HostSystemUpdateExecutionException::invalidStartStatus($status);
        }
    }

    private function buildFailureContext(
        HostSystemUpdateExecution $execution,
        string $previousStatus,
        Throwable $throwable,
        bool $maintenanceEntered,
    ): HostSystemUpdateFailureContext {
        $failedStep = $this->resolveFailedStep($execution, $throwable, $maintenanceEntered);
        $requiresRollback = $this->resolveRequiresRollback($execution, $throwable);

        if ($throwable instanceof HostSystemUpdateFileExecutionException) {
            return new HostSystemUpdateFailureContext(
                failedStep: $failedStep,
                previousStatus: $previousStatus,
                errorCode: $throwable->errorCode(),
                userFacingMessage: $throwable->userFacingMessage(),
                requiresRollback: $requiresRollback,
                exceptionClass: $throwable::class,
            );
        }

        if ($throwable instanceof HostSystemUpdatePostApplyException) {
            return new HostSystemUpdateFailureContext(
                failedStep: $failedStep,
                previousStatus: $previousStatus,
                errorCode: $throwable->errorCode(),
                userFacingMessage: $throwable->userFacingMessage(),
                requiresRollback: $requiresRollback,
                exceptionClass: $throwable::class,
            );
        }

        if ($throwable instanceof HostSystemUpdateStagingException) {
            return new HostSystemUpdateFailureContext(
                failedStep: $failedStep,
                previousStatus: $previousStatus,
                errorCode: $throwable->errorCode(),
                userFacingMessage: $throwable->userFacingMessage(),
                requiresRollback: false,
                exceptionClass: $throwable::class,
            );
        }

        if ($throwable instanceof HostSystemUpdateRecoveryException) {
            return new HostSystemUpdateFailureContext(
                failedStep: $failedStep,
                previousStatus: $previousStatus,
                errorCode: $throwable->errorCode(),
                userFacingMessage: $throwable->userFacingMessage(),
                requiresRollback: $requiresRollback,
                exceptionClass: $throwable::class,
            );
        }

        return new HostSystemUpdateFailureContext(
            failedStep: $failedStep,
            previousStatus: $previousStatus,
            errorCode: 'host_system_update_execution_failed',
            userFacingMessage: (string) __('dberp.system_update_center.notifications.execution_failed_body'),
            requiresRollback: $requiresRollback,
            exceptionClass: $throwable::class,
        );
    }

    private function resolveFailedStep(
        HostSystemUpdateExecution $execution,
        Throwable $throwable,
        bool $maintenanceEntered,
    ): string {
        if ($throwable instanceof HostSystemUpdateRecoveryException && ! $maintenanceEntered) {
            return HostSystemUpdateExecutionStep::MaintenanceDown->value;
        }

        $currentStep = (string) ($execution->current_step ?? '');

        if ($currentStep !== '') {
            return $currentStep;
        }

        return HostSystemUpdateExecutionStep::Precheck->value;
    }

    private function resolveRequiresRollback(HostSystemUpdateExecution $execution, Throwable $throwable): bool
    {
        if ((string) ($execution->status ?? '') === HostSystemUpdateExecutionStatus::RollbackRequired->value) {
            return true;
        }

        if ($throwable instanceof HostSystemUpdateFileExecutionException) {
            return $throwable->partialChangesApplied();
        }

        if ($throwable instanceof HostSystemUpdatePostApplyException) {
            return $throwable->requiresRollback();
        }

        return false;
    }

    private function resolveReportErrorCode(Throwable $throwable, HostSystemUpdateExecution $execution): ?string
    {
        if (method_exists($throwable, 'errorCode')) {
            /** @var mixed $code */
            $code = $throwable->errorCode();

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

        return $execution->error_code;
    }

    private function resolveReportUserMessage(Throwable $throwable, HostSystemUpdateExecution $execution): ?string
    {
        if (method_exists($throwable, 'userFacingMessage')) {
            /** @var mixed $message */
            $message = $throwable->userFacingMessage();

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

        $stored = $execution->error_message;

        return is_string($stored) && trim($stored) !== '' ? trim($stored) : null;
    }
}
