<?php

declare(strict_types=1);

namespace App\Services\SupportCenter;

use App\Enums\SupportCenter\HostSystemUpdateExecutionStatus;
use App\Enums\SupportCenter\HostSystemUpdateExecutionStep;
use App\Models\HostSystemUpdateBackup;
use App\Models\HostSystemUpdateExecution;

/**
 * 更新失败捕获、状态标记、维护模式恢复与条件触发回滚。
 */
final class HostSystemUpdateFailureHandler
{
    public function __construct(
        private readonly HostSystemUpdateExecutionStateMachine $stateMachine,
        private readonly HostSystemUpdateRollbackService $rollbackService,
        private readonly HostSystemUpdateMaintenanceModeService $maintenanceModeService,
        private readonly HostSystemUpdateDatabaseRollbackBoundary $databaseRollbackBoundary,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function handle(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateFailureContext $context,
    ): HostSystemUpdateExecution {
        $execution = $this->markFailureState($execution, $context);

        $this->auditLogger->logSystemUpdateFailureCaptured($execution, $context);

        $this->executionLogger->logStepFailed(
            $execution,
            $context->failedStep,
            $context->userFacingMessage,
            $context->errorCode,
            $context->userFacingMessage,
            $this->safeContextForLog($context),
        );

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

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

        if ($this->shouldTriggerRollback($fresh, $context)) {
            return $this->rollbackService->rollback($fresh);
        }

        if ($this->databaseRollbackBoundary->requiresManualDatabaseRecovery($fresh)) {
            $fresh->fill([
                'error_message' => trim((string) $fresh->error_message.' '.$this->databaseRollbackBoundary->manualDatabaseRecoveryMessage()),
            ]);
            $fresh->save();
        }

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

    private function markFailureState(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateFailureContext $context,
    ): HostSystemUpdateExecution {
        $currentStatus = (string) ($execution->status ?? '');

        if ($currentStatus === HostSystemUpdateExecutionStatus::RollbackRequired->value) {
            return $execution;
        }

        $failedStep = HostSystemUpdateExecutionStep::tryFrom($context->failedStep);

        if ($context->requiresRollback && $context->failedDuringOrAfterFileChanges()) {
            return $this->stateMachine->requireRollback(
                $execution,
                $context->errorCode,
                $context->userFacingMessage,
                $this->safeContextForLog($context),
                $failedStep ?? HostSystemUpdateExecutionStep::ReplaceFiles,
            );
        }

        if ($currentStatus === HostSystemUpdateExecutionStatus::Failed->value) {
            return $execution;
        }

        return $this->stateMachine->fail(
            $execution,
            $context->errorCode,
            $context->userFacingMessage,
            $this->safeContextForLog($context),
        );
    }

    private function shouldTriggerRollback(
        HostSystemUpdateExecution $execution,
        HostSystemUpdateFailureContext $context,
    ): bool {
        if ((string) ($execution->status ?? '') !== HostSystemUpdateExecutionStatus::RollbackRequired->value) {
            return false;
        }

        if (! $context->requiresRollback) {
            return false;
        }

        return $this->hasUsableFileBackup($execution);
    }

    private function hasUsableFileBackup(HostSystemUpdateExecution $execution): bool
    {
        return $execution->backups()
            ->where('backup_type', HostSystemUpdateBackup::TYPE_FILE_SNAPSHOT)
            ->where('status', HostSystemUpdateBackup::STATUS_COMPLETED)
            ->exists();
    }

    /**
     * @return array<string, mixed>|null
     */
    private function safeContextForLog(HostSystemUpdateFailureContext $context): ?array
    {
        $payload = array_filter([
            'failed_step' => $context->failedStep,
            'previous_status' => $context->previousStatus,
            'exception_class' => $context->exceptionClass,
            'command_exit_code' => $context->commandExitCode,
        ], static fn (mixed $value): bool => $value !== null && $value !== '');

        if ($context->safeContext !== null && $context->safeContext !== []) {
            $payload = array_merge($payload, SupportCenterSnapshotSanitizer::sanitize($context->safeContext));
        }

        return $payload === [] ? null : $payload;
    }
}
