<?php

declare(strict_types=1);

/**
 * 本文件属于 DBErp 进销存系统。
 *
 * 版权所有 © 2026 北京珑大钜商科技有限公司。保留所有权利。
 *
 * 未经官方授权，不得删除、篡改或隐藏本文件中的版权声明、产品标识与来源信息。
 * 商业授权、官方更新与技术支持服务，请以珑大官方支持中心记录为准。
 *
 * @link https://support.loongdom.cn
 * @see  https://www.dberp.com.cn
 */

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 HostSystemUpdateMigrationStateStore $migrationStateStore,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly HostSystemUpdateWorkspaceCleanupService $workspaceCleanupService,
        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),
        );

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

        if ($this->shouldTriggerRollback($fresh, $context)) {
            $rolled = $this->rollbackService->rollback($fresh);
            $this->workspaceCleanupService->cleanupAfterTerminalFailure(
                $rolled->fresh(['package', 'backups']) ?? $rolled,
            );

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

        if ((string) $fresh->status !== HostSystemUpdateExecutionStatus::ManualRecoveryRequired->value
            && $this->databaseRollbackBoundary->requiresManualDatabaseRecovery($fresh)) {
            $fresh->fill([
                'error_message' => trim((string) $fresh->error_message.' '.$this->databaseRollbackBoundary->manualDatabaseRecoveryMessage()),
            ]);
            $fresh->save();
        }

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

        if (! $this->shouldRemainInMaintenanceForRecovery($result)) {
            $this->maintenanceModeService->attemptLeaveMaintenanceOnFailure($result);
        }

        $this->workspaceCleanupService->cleanupAfterTerminalFailure($result);

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

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

        // FinalizeService 可能已先标成人工恢复或 Failed；状态机不允许自转换，Failed 也不能升到 manual_recovery_required。
        if (in_array($currentStatus, [
            HostSystemUpdateExecutionStatus::ManualRecoveryRequired->value,
            HostSystemUpdateExecutionStatus::Failed->value,
        ], true)) {
            return $execution;
        }

        if ($this->failureMayFollowDatabaseChanges($context)
            && $this->migrationStateStore->hasAppliedMigrationsSinceBaseline($execution)) {
            return $this->stateMachine->requireManualRecovery(
                $execution,
                $context->errorCode,
                trim($context->userFacingMessage.' '.$this->databaseRollbackBoundary->manualDatabaseRecoveryMessage()),
                array_merge($this->safeContextForLog($context) ?? [], [
                    'database_changes_detected' => true,
                    'automatic_file_rollback' => false,
                ]),
                $failedStep ?? HostSystemUpdateExecutionStep::RunMigrations,
            );
        }

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

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

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

    private function failureMayFollowDatabaseChanges(HostSystemUpdateFailureContext $context): bool
    {
        return in_array($context->failedStep, [
            HostSystemUpdateExecutionStep::RunMigrations->value,
            HostSystemUpdateExecutionStep::ClearCache->value,
            HostSystemUpdateExecutionStep::PostUpdateHealthCheck->value,
            HostSystemUpdateExecutionStep::WriteVersion->value,
            HostSystemUpdateExecutionStep::CommitVendorBackup->value,
        ], true);
    }

    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();
    }

    private function shouldRemainInMaintenanceForRecovery(HostSystemUpdateExecution $execution): bool
    {
        if (method_exists($this->maintenanceModeService, 'shouldRemainInMaintenanceForRecovery')) {
            return $this->maintenanceModeService->shouldRemainInMaintenanceForRecovery($execution);
        }

        return in_array((string) ($execution->status ?? ''), [
            HostSystemUpdateExecutionStatus::RollbackRequired->value,
            HostSystemUpdateExecutionStatus::RollingBack->value,
            HostSystemUpdateExecutionStatus::RollbackFailed->value,
            HostSystemUpdateExecutionStatus::ManualRecoveryRequired->value,
        ], true);
    }

    /**
     * @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;
    }
}
