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

/**
 * 更新失败回滚编排（文件/env 恢复，不自动数据库回滚）。
 */
final class HostSystemUpdateRollbackService
{
    public function __construct(
        private readonly HostSystemUpdateExecutionStateMachine $stateMachine,
        private readonly HostSystemUpdateFileRestoreService $fileRestoreService,
        private readonly HostSystemUpdateVendorSnapshotService $vendorSnapshotService,
        private readonly HostSystemUpdateRuntimeResetService $runtimeResetService,
        private readonly HostSystemUpdateEnvRestoreService $envRestoreService,
        private readonly HostSystemUpdateDatabaseRollbackBoundary $databaseRollbackBoundary,
        private readonly HostSystemUpdateMaintenanceModeService $maintenanceModeService,
        private readonly HostSystemUpdateExecutionLogger $executionLogger,
        private readonly SupportCenterAuditLogger $auditLogger,
    ) {}

    public function rollback(HostSystemUpdateExecution $execution): HostSystemUpdateExecution
    {
        $this->assertRollbackAllowed($execution);

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

        $needsManualDatabaseRecovery = $this->databaseRollbackBoundary->requiresManualDatabaseRecovery($execution);

        try {
            $execution = $this->stateMachine->startRollback(
                $execution,
                (string) __('dberp.system_update.recovery.log.rollback_started'),
            );

            if ($needsManualDatabaseRecovery) {
                $this->executionLogger->logTransition(
                    $execution,
                    HostSystemUpdateExecutionStep::Rollback->value,
                    $this->databaseRollbackBoundary->manualDatabaseRecoveryMessage(),
                    ['database_rollback' => 'manual_required'],
                );
            }

            $this->vendorSnapshotService->restore($execution);
            $this->fileRestoreService->restoreFromBackup($execution->fresh(['backups']) ?? $execution);
            $this->envRestoreService->restoreVersionKeys($execution->fresh(['backups']) ?? $execution);
            try {
                $this->runtimeResetService->reset($execution);
            } catch (\Throwable) {
                // 文件/env 已恢复时，Web OPcache 刷新失败不能反过来把回滚判为失败。
                // 当前请求结束后由新请求重新载入；恢复结果以磁盘状态为准。
            }

            $execution = $this->stateMachine->completeRollback(
                $execution->fresh(['backups']) ?? $execution,
                (string) __('dberp.system_update.recovery.log.rollback_completed'),
            );

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

            return $execution->fresh(['backups']) ?? $execution;
        } catch (HostSystemUpdateRecoveryException $exception) {
            $execution = $this->stateMachine->failRollback(
                $execution->fresh() ?? $execution,
                $exception->errorCode(),
                $exception->userFacingMessage(),
            );

            $this->auditLogger->logSystemUpdateRollbackFailed($execution, $exception);

            throw $exception;
        } finally {
            $latest = $execution->fresh() ?? $execution;

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

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

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

        if (! in_array($status, [
            HostSystemUpdateExecutionStatus::RollbackRequired->value,
            HostSystemUpdateExecutionStatus::RollingBack->value,
        ], true)) {
            throw HostSystemUpdateRecoveryException::rollbackNotAllowed($status);
        }
    }
}
