<?php

declare(strict_types=1);

namespace App\Jobs;

use App\Jobs\Concerns\BridgesFailedOperationRun;
use App\Jobs\Middleware\TrackOperationRun;
use App\Models\BaselineProfile;
use App\Models\BaselineSnapshot;
use App\Models\BaselineSnapshotItem;
use App\Models\Finding;
use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Baselines\BaselineAutoCloseService;
use App\Services\Baselines\BaselineContentCapturePhase;
use App\Services\Baselines\BaselineSnapshotIdentity;
use App\Services\Baselines\BaselineSnapshotTruthResolver;
use App\Services\Baselines\CurrentStateHashResolver;
use App\Services\Baselines\Evidence\ContentEvidenceProvider;
use App\Services\Baselines\Evidence\EvidenceProvenance;
use App\Services\Baselines\Evidence\MetaEvidenceProvider;
use App\Services\Baselines\Evidence\ResolvedEvidence;
use App\Services\Findings\FindingSlaPolicy;
use App\Services\Findings\FindingWorkflowService;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Services\Settings\SettingsResolver;
use App\Support\Baselines\BaselineCaptureMode;
use App\Support\Baselines\BaselineCompareReasonCode;
use App\Support\Baselines\BaselineFullContentRolloutGate;
use App\Support\Baselines\BaselineReasonCodes;
use App\Support\Baselines\BaselineScope;
use App\Support\Baselines\BaselineSubjectKey;
use App\Support\Baselines\Compare\CompareFindingCandidate;
use App\Support\Baselines\Compare\CompareOrchestrationContext;
use App\Support\Baselines\Compare\CompareState;
use App\Support\Baselines\Compare\CompareStrategyRegistry;
use App\Support\Baselines\Compare\CompareStrategySelection;
use App\Support\Baselines\Compare\CompareSubjectResult;
use App\Support\Baselines\Compare\StrategySelectionState;
use App\Support\Baselines\PolicyVersionCapturePurpose;
use App\Support\Baselines\SubjectResolver;
use App\Support\Inventory\InventoryCoverage;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\ReasonTranslation\ReasonPresenter;
use Carbon\CarbonImmutable;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use RuntimeException;

class CompareBaselineToTenantJob implements ShouldQueue
{
    use BridgesFailedOperationRun;
    use Dispatchable;
    use InteractsWithQueue;
    use Queueable;
    use SerializesModels;

    public int $timeout = 300;

    public bool $failOnTimeout = true;

    public ?OperationRun $operationRun = null;

    public function __construct(
        public OperationRun $run,
    ) {
        $this->operationRun = $run;
    }

    /**
     * @return array<int, object>
     */
    public function middleware(): array
    {
        return [new TrackOperationRun];
    }

    public function handle(
        BaselineSnapshotIdentity $snapshotIdentity,
        AuditLogger $auditLogger,
        OperationRunService $operationRunService,
        ?SettingsResolver $settingsResolver = null,
        ?BaselineAutoCloseService $baselineAutoCloseService = null,
        ?CurrentStateHashResolver $hashResolver = null,
        ?BaselineSnapshotTruthResolver $snapshotTruthResolver = null,
        ?MetaEvidenceProvider $metaEvidenceProvider = null,
        ?BaselineContentCapturePhase $contentCapturePhase = null,
        ?BaselineFullContentRolloutGate $rolloutGate = null,
        ?ContentEvidenceProvider $contentEvidenceProvider = null,
        ?CompareStrategyRegistry $compareStrategyRegistry = null,
    ): void {
        $settingsResolver ??= app(SettingsResolver::class);
        $baselineAutoCloseService ??= app(BaselineAutoCloseService::class);
        $hashResolver ??= app(CurrentStateHashResolver::class);
        $snapshotTruthResolver ??= app(BaselineSnapshotTruthResolver::class);
        $metaEvidenceProvider ??= app(MetaEvidenceProvider::class);
        $contentCapturePhase ??= app(BaselineContentCapturePhase::class);
        $rolloutGate ??= app(BaselineFullContentRolloutGate::class);
        $contentEvidenceProvider ??= app(ContentEvidenceProvider::class);
        $compareStrategyRegistry ??= app(CompareStrategyRegistry::class);

        if (! $this->operationRun instanceof OperationRun) {
            $this->fail(new RuntimeException('OperationRun context is required for CompareBaselineToTenantJob.'));

            return;
        }

        $context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $profileId = (int) ($context['baseline_profile_id'] ?? 0);
        $snapshotId = (int) ($context['baseline_snapshot_id'] ?? 0);

        $profile = BaselineProfile::query()->find($profileId);

        if (! $profile instanceof BaselineProfile) {
            throw new RuntimeException("BaselineProfile #{$profileId} not found.");
        }

        $tenant = ManagedEnvironment::query()->find($this->operationRun->managed_environment_id);

        if (! $tenant instanceof ManagedEnvironment) {
            throw new RuntimeException("ManagedEnvironment #{$this->operationRun->managed_environment_id} not found.");
        }

        $workspace = Workspace::query()->whereKey((int) $tenant->workspace_id)->first();

        if (! $workspace instanceof Workspace) {
            throw new RuntimeException("Workspace #{$tenant->workspace_id} not found.");
        }

        $initiator = $this->operationRun->user_id
            ? User::query()->find($this->operationRun->user_id)
            : null;

        $effectiveScope = BaselineScope::fromJsonb($context['effective_scope'] ?? null);
        $effectiveTypes = $this->truthfulTypesFromContext($context, $effectiveScope);
        $scopeKey = 'baseline_profile:'.$profile->getKey();

        $captureMode = $profile->capture_mode instanceof BaselineCaptureMode
            ? $profile->capture_mode
            : BaselineCaptureMode::Opportunistic;

        if ($captureMode === BaselineCaptureMode::FullContent) {
            try {
                $rolloutGate->assertEnabled();
            } catch (RuntimeException) {
                $this->auditStarted(
                    auditLogger: $auditLogger,
                    tenant: $tenant,
                    profile: $profile,
                    initiator: $initiator,
                    captureMode: $captureMode,
                    subjectsTotal: 0,
                    effectiveScope: $effectiveScope,
                );

                $effectiveTypeCount = count($effectiveTypes);
                $gapCount = max(1, $effectiveTypeCount);

                $this->completeWithCoverageWarning(
                    operationRunService: $operationRunService,
                    auditLogger: $auditLogger,
                    tenant: $tenant,
                    profile: $profile,
                    initiator: $initiator,
                    inventorySyncRun: null,
                    coverageProof: false,
                    effectiveTypes: $effectiveTypes,
                    coveredTypes: [],
                    uncoveredTypes: $effectiveTypes,
                    errorsRecorded: $gapCount,
                    captureMode: $captureMode,
                    reasonCode: BaselineCompareReasonCode::RolloutDisabled,
                    evidenceGapsByReason: [
                        BaselineCompareReasonCode::RolloutDisabled->value => $gapCount,
                    ],
                );

                return;
            }
        }

        if ($effectiveTypes === []) {
            $this->auditStarted(
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                captureMode: $captureMode,
                subjectsTotal: 0,
                effectiveScope: $effectiveScope,
            );

            $this->completeWithCoverageWarning(
                operationRunService: $operationRunService,
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                inventorySyncRun: null,
                coverageProof: false,
                effectiveTypes: [],
                coveredTypes: [],
                uncoveredTypes: [],
                errorsRecorded: 1,
                captureMode: $captureMode,
                reasonCode: BaselineCompareReasonCode::NoSubjectsInScope,
                evidenceGapsByReason: [],
            );

            return;
        }

        $inventorySyncRun = $this->resolveLatestInventorySyncRun($tenant);
        $coverage = $inventorySyncRun instanceof OperationRun
            ? InventoryCoverage::fromContext($inventorySyncRun->context)
            : null;

        if (! $inventorySyncRun instanceof OperationRun || ! $coverage instanceof InventoryCoverage) {
            $this->auditStarted(
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                captureMode: $captureMode,
                subjectsTotal: 0,
                effectiveScope: $effectiveScope,
            );

            $this->completeWithCoverageWarning(
                operationRunService: $operationRunService,
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                inventorySyncRun: $inventorySyncRun,
                coverageProof: false,
                effectiveTypes: $effectiveTypes,
                coveredTypes: [],
                uncoveredTypes: $effectiveTypes,
                errorsRecorded: count($effectiveTypes),
                captureMode: $captureMode,
            );

            return;
        }

        $coveredTypes = array_values(array_intersect($effectiveTypes, $coverage->coveredTypes()));
        $uncoveredTypes = array_values(array_diff($effectiveTypes, $coveredTypes));

        if ($coveredTypes === []) {
            $this->auditStarted(
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                captureMode: $captureMode,
                subjectsTotal: 0,
                effectiveScope: $effectiveScope,
            );

            $this->completeWithCoverageWarning(
                operationRunService: $operationRunService,
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                inventorySyncRun: $inventorySyncRun,
                coverageProof: true,
                effectiveTypes: $effectiveTypes,
                coveredTypes: [],
                uncoveredTypes: $effectiveTypes,
                errorsRecorded: count($effectiveTypes),
                captureMode: $captureMode,
            );

            return;
        }

        $snapshot = BaselineSnapshot::query()
            ->where('workspace_id', (int) $profile->workspace_id)
            ->where('baseline_profile_id', (int) $profile->getKey())
            ->whereKey($snapshotId)
            ->first();

        if (! $snapshot instanceof BaselineSnapshot) {
            throw new RuntimeException("BaselineSnapshot #{$snapshotId} not found.");
        }

        $snapshotResolution = $snapshotTruthResolver->resolveCompareSnapshot($profile, $snapshot);

        if (! ($snapshotResolution['ok'] ?? false)) {
            $reasonCode = is_string($snapshotResolution['reason_code'] ?? null)
                ? (string) $snapshotResolution['reason_code']
                : BaselineReasonCodes::COMPARE_NO_CONSUMABLE_SNAPSHOT;

            $context = is_array($this->operationRun->context) ? $this->operationRun->context : [];
            $context['baseline_compare'] = array_merge(
                is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [],
                [
                    'reason_code' => $reasonCode,
                    'effective_snapshot_id' => $snapshotResolution['effective_snapshot']?->getKey(),
                    'latest_attempted_snapshot_id' => $snapshotResolution['latest_attempted_snapshot']?->getKey(),
                ],
            );
            $context['result'] = array_merge(
                is_array($context['result'] ?? null) ? $context['result'] : [],
                [
                    'snapshot_id' => (int) $snapshot->getKey(),
                ],
            );
            $context = $this->withCompareReasonTranslation($context, $reasonCode);

            $this->operationRun->update(['context' => $context]);
            $this->operationRun->refresh();

            $operationRunService->finalizeBlockedRun(
                run: $this->operationRun,
                reasonCode: $reasonCode,
                message: $this->snapshotBlockedMessage($reasonCode),
            );

            return;
        }

        /** @var BaselineSnapshot $snapshot */
        $snapshot = $snapshotResolution['snapshot'];
        $snapshotId = (int) $snapshot->getKey();

        $strategySelection = $compareStrategyRegistry->select($effectiveScope);
        $context = $this->withCompareStrategySelection($context, $strategySelection);
        $context['progress'] = array_merge(
            is_array($context['progress'] ?? null) ? $context['progress'] : [],
            [
                'phase' => [
                    'key' => 'preparing',
                    'label' => 'Preparing baseline comparison.',
                ],
            ],
        );
        $this->operationRun->update(['context' => $context]);
        $this->operationRun->refresh();

        if (! $strategySelection->isSupported()) {
            $this->auditStarted(
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                captureMode: $captureMode,
                subjectsTotal: 0,
                effectiveScope: $effectiveScope,
            );

            $this->completeWithCoverageWarning(
                operationRunService: $operationRunService,
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                inventorySyncRun: $inventorySyncRun,
                coverageProof: true,
                effectiveTypes: $effectiveTypes,
                coveredTypes: $coveredTypes,
                uncoveredTypes: $uncoveredTypes,
                errorsRecorded: max(1, count($coveredTypes !== [] ? $coveredTypes : $effectiveTypes)),
                captureMode: $captureMode,
                reasonCode: BaselineCompareReasonCode::UnsupportedSubjects,
                evidenceGapsByReason: [
                    $this->strategySelectionGapReason($strategySelection) => max(1, count($coveredTypes !== [] ? $coveredTypes : $effectiveTypes)),
                ],
            );

            return;
        }

        $since = $snapshot->captured_at instanceof \DateTimeInterface
            ? CarbonImmutable::instance($snapshot->captured_at)
            : null;

        $baselineResult = $this->loadBaselineItems($snapshotId, $coveredTypes);
        $baselineItems = $baselineResult['items'];
        $baselineGaps = $baselineResult['gaps'];

        $currentResult = $this->loadCurrentInventory($tenant, $coveredTypes, (int) $inventorySyncRun->getKey());
        $currentItems = $currentResult['items'];
        $currentGaps = $currentResult['gaps'];

        $ambiguousKeys = array_values(array_unique(array_filter(array_merge(
            is_array($baselineResult['ambiguous_keys'] ?? null) ? $baselineResult['ambiguous_keys'] : [],
            is_array($currentResult['ambiguous_keys'] ?? null) ? $currentResult['ambiguous_keys'] : [],
        ), 'is_string')));

        foreach ($ambiguousKeys as $ambiguousKey) {
            unset($baselineItems[$ambiguousKey], $currentItems[$ambiguousKey]);
        }

        $subjects = array_values(array_map(
            static fn (array $item): array => [
                'policy_type' => (string) $item['policy_type'],
                'subject_external_id' => (string) $item['subject_external_id'],
                'subject_key' => (string) $item['subject_key'],
            ],
            $currentItems,
        ));

        $subjectsTotal = count($subjects);

        $this->auditStarted(
            auditLogger: $auditLogger,
            tenant: $tenant,
            profile: $profile,
            initiator: $initiator,
            captureMode: $captureMode,
            subjectsTotal: $subjectsTotal,
            effectiveScope: $effectiveScope,
        );

        $phaseStats = [
            'requested' => 0,
            'succeeded' => 0,
            'skipped' => 0,
            'failed' => 0,
            'throttled' => 0,
        ];
        $phaseResult = [];
        $phaseGaps = [];
        $phaseGapSubjects = [];
        $resumeToken = null;

        if ($captureMode === BaselineCaptureMode::FullContent) {
            $processingContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
            $processingContext['progress'] = array_merge(
                is_array($processingContext['progress'] ?? null) ? $processingContext['progress'] : [],
                [
                    'phase' => [
                        'key' => 'processing',
                        'label' => 'Refreshing comparison evidence.',
                    ],
                ],
            );
            $this->operationRun->update(['context' => $processingContext]);
            $this->operationRun->refresh();

            $budgets = [
                'max_items_per_run' => (int) config('tenantpilot.baselines.full_content_capture.max_items_per_run', 200),
                'max_concurrency' => (int) config('tenantpilot.baselines.full_content_capture.max_concurrency', 5),
                'max_retries' => (int) config('tenantpilot.baselines.full_content_capture.max_retries', 3),
            ];

            $resumeTokenIn = null;

            if (is_array($context['baseline_compare'] ?? null)) {
                $resumeTokenIn = $context['baseline_compare']['resume_token'] ?? null;
            }

            $phaseResult = $contentCapturePhase->capture(
                tenant: $tenant,
                subjects: $subjects,
                purpose: PolicyVersionCapturePurpose::BaselineCompare,
                budgets: $budgets,
                resumeToken: is_string($resumeTokenIn) ? $resumeTokenIn : null,
                operationRunId: (int) $this->operationRun->getKey(),
                baselineProfileId: (int) $profile->getKey(),
                createdBy: $initiator?->email,
            );

            $phaseStats = is_array($phaseResult['stats'] ?? null) ? $phaseResult['stats'] : $phaseStats;
            $phaseGaps = is_array($phaseResult['gaps'] ?? null) ? $phaseResult['gaps'] : [];
            $phaseGapSubjects = is_array($phaseResult['gap_subjects'] ?? null) ? $phaseResult['gap_subjects'] : [];
            $resumeToken = is_string($phaseResult['resume_token'] ?? null) ? $phaseResult['resume_token'] : null;
        }

        $resolvedCurrentEvidenceByExternalId = $hashResolver->resolveForSubjects(
            tenant: $tenant,
            subjects: $subjects,
            since: $since,
            latestInventorySyncRunId: (int) $inventorySyncRun->getKey(),
        );

        $resolvedCurrentEvidenceByExternalId = array_replace(
            $resolvedCurrentEvidenceByExternalId,
            $this->resolveCapturedCurrentEvidenceByExternalId($phaseResult),
        );

        $resolvedCurrentMetaEvidenceByExternalId = $metaEvidenceProvider->resolve(
            tenant: $tenant,
            subjects: $subjects,
            since: $since,
            latestInventorySyncRunId: (int) $inventorySyncRun->getKey(),
        );

        $resolvedCurrentEvidence = $this->rekeyResolvedEvidenceBySubjectKey(
            currentItems: $currentItems,
            resolvedByExternalId: $resolvedCurrentEvidenceByExternalId,
        );

        $resolvedCurrentMetaEvidence = $this->rekeyResolvedEvidenceBySubjectKey(
            currentItems: $currentItems,
            resolvedByExternalId: $resolvedCurrentMetaEvidenceByExternalId,
        );

        $resolvedEffectiveCurrentEvidence = $this->resolveEffectiveCurrentEvidence(
            baselineItems: $baselineItems,
            currentItems: $currentItems,
            resolvedBestEvidence: $resolvedCurrentEvidence,
            resolvedMetaEvidence: $resolvedCurrentMetaEvidence,
        );

        $strategy = $compareStrategyRegistry->resolve($strategySelection->strategyKey);
        $orchestrationContext = new CompareOrchestrationContext(
            workspaceId: (int) $workspace->getKey(),
            tenantId: (int) $tenant->getKey(),
            baselineProfileId: (int) $profile->getKey(),
            baselineSnapshotId: (int) $snapshot->getKey(),
            operationRunId: (int) $this->operationRun->getKey(),
            normalizedScope: $effectiveScope->toStoredJsonb(),
            strategySelection: $strategySelection,
            coverageContext: [
                'inventory_sync_run_id' => (int) $inventorySyncRun->getKey(),
                'effective_types' => $effectiveTypes,
                'covered_types' => $coveredTypes,
                'uncovered_types' => $uncoveredTypes,
            ],
            launchContext: is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [],
        );

        $comparePhaseContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $comparePhaseContext['progress'] = array_merge(
            is_array($comparePhaseContext['progress'] ?? null) ? $comparePhaseContext['progress'] : [],
            [
                'phase' => [
                    'key' => 'processing',
                    'label' => 'Evaluating baseline drift.',
                ],
            ],
        );
        $this->operationRun->update(['context' => $comparePhaseContext]);
        $this->operationRun->refresh();

        try {
            $compareResult = $strategy->compare(
                context: $orchestrationContext,
                tenant: $tenant,
                baselineItems: $baselineItems,
                currentItems: $currentItems,
                resolvedCurrentEvidence: $resolvedEffectiveCurrentEvidence,
                severityMapping: $this->resolveSeverityMapping($workspace, $settingsResolver),
            );
        } catch (\Throwable $exception) {
            $failedContext = $this->withCompareStrategyDiagnostics(
                context: is_array($this->operationRun->context) ? $this->operationRun->context : [],
                strategySelection: $strategySelection,
                executionDiagnostics: [
                    'failed' => true,
                    'exception_class' => $exception::class,
                ],
            );

            $this->operationRun->update(['context' => $failedContext]);
            $this->operationRun->refresh();

            $this->completeWithCoverageWarning(
                operationRunService: $operationRunService,
                auditLogger: $auditLogger,
                tenant: $tenant,
                profile: $profile,
                initiator: $initiator,
                inventorySyncRun: $inventorySyncRun,
                coverageProof: true,
                effectiveTypes: $effectiveTypes,
                coveredTypes: $coveredTypes,
                uncoveredTypes: $uncoveredTypes,
                errorsRecorded: max(1, count($coveredTypes !== [] ? $coveredTypes : $effectiveTypes)),
                captureMode: $captureMode,
                reasonCode: BaselineCompareReasonCode::StrategyFailed,
                evidenceGapsByReason: [
                    'strategy_failed' => max(1, count($coveredTypes !== [] ? $coveredTypes : $effectiveTypes)),
                ],
            );

            return;
        }

        $normalizedStrategyResults = $this->normalizeStrategySubjectResults($compareResult['subject_results'] ?? []);
        $driftResults = $normalizedStrategyResults['drift_results'];
        $driftGaps = $normalizedStrategyResults['gap_counts'];
        $rbacRoleDefinitionSummary = is_array($compareResult['diagnostics']['rbac_role_definitions'] ?? null)
            ? $compareResult['diagnostics']['rbac_role_definitions']
            : $this->emptyRbacRoleDefinitionSummary();
        $strategyGapSubjects = $normalizedStrategyResults['gap_subjects'];
        $strategyStateCounts = $normalizedStrategyResults['state_counts'];
        $strategyDiagnostics = is_array($compareResult['diagnostics'] ?? null)
            ? $compareResult['diagnostics']
            : [];

        $upsertResult = $this->upsertFindings(
            $tenant,
            $profile,
            $scopeKey,
            $driftResults,
        );

        $severityBreakdown = $this->countBySeverity($driftResults);
        $countsByChangeType = $this->countByChangeType($driftResults);

        $gapsByReason = $this->mergeGapCounts($baselineGaps, $currentGaps, $phaseGaps, $driftGaps);
        $gapsCount = array_sum($gapsByReason);

        $gapSubjects = $this->collectGapSubjects(
            ambiguousKeys: $ambiguousKeys,
            phaseGapSubjects: $phaseGapSubjects ?? [],
            driftGapSubjects: $strategyGapSubjects,
        );

        $summaryCounts = [
            'total' => count($driftResults),
            'processed' => count($driftResults),
            'succeeded' => (int) $upsertResult['processed_count'],
            'failed' => 0,
            'errors_recorded' => count($uncoveredTypes),
            'high' => $severityBreakdown[Finding::SEVERITY_HIGH] ?? 0,
            'medium' => $severityBreakdown[Finding::SEVERITY_MEDIUM] ?? 0,
            'low' => $severityBreakdown[Finding::SEVERITY_LOW] ?? 0,
            'findings_created' => (int) $upsertResult['created_count'],
            'findings_reopened' => (int) $upsertResult['reopened_count'],
            'findings_unchanged' => (int) $upsertResult['unchanged_count'],
        ];

        $warningsRecorded = $uncoveredTypes !== [] || $resumeToken !== null || $gapsByReason !== [];
        $outcome = $warningsRecorded ? OperationRunOutcome::PartiallySucceeded->value : OperationRunOutcome::Succeeded->value;

        $operationRunService->updateRun(
            $this->operationRun,
            status: OperationRunStatus::Completed->value,
            outcome: $outcome,
            summaryCounts: $summaryCounts,
        );

        $resolvedCount = 0;

        if ($baselineAutoCloseService->shouldAutoClose($tenant, $this->operationRun)) {
            $resolvedCount = $baselineAutoCloseService->resolveStaleFindings(
                tenant: $tenant,
                baselineProfileId: (int) $profile->getKey(),
                seenFingerprints: $upsertResult['seen_fingerprints'],
                currentOperationRunId: (int) $this->operationRun->getKey(),
            );

            $summaryCounts['findings_resolved'] = $resolvedCount;

            $operationRunService->updateRun(
                $this->operationRun,
                status: OperationRunStatus::Completed->value,
                outcome: $outcome,
                summaryCounts: $summaryCounts,
            );
        }

        $coverageBreakdown = $this->summarizeCurrentEvidenceCoverage($currentItems, $resolvedEffectiveCurrentEvidence);
        $baselineCoverage = $this->summarizeBaselineEvidenceCoverage($baselineItems);

        $overallFidelity = ($baselineCoverage['baseline_meta'] ?? 0) > 0
            || ($coverageBreakdown['resolved_meta'] ?? 0) > 0
            || ($gapsByReason['missing_current'] ?? 0) > 0
            ? EvidenceProvenance::FidelityMeta
            : EvidenceProvenance::FidelityContent;

        $reasonCode = null;

        if ($subjectsTotal === 0) {
            $reasonCode = BaselineCompareReasonCode::NoSubjectsInScope;
        } elseif (count($driftResults) === 0) {
            $reasonCode = match (true) {
                $uncoveredTypes !== [] => BaselineCompareReasonCode::CoverageUnproven,
                ($strategyStateCounts[CompareState::Failed->value] ?? 0) > 0 => BaselineCompareReasonCode::StrategyFailed,
                ($strategyStateCounts[CompareState::Ambiguous->value] ?? 0) > 0 => BaselineCompareReasonCode::AmbiguousSubjects,
                ($strategyStateCounts[CompareState::Unsupported->value] ?? 0) > 0 => BaselineCompareReasonCode::UnsupportedSubjects,
                $resumeToken !== null || $gapsCount > 0 => BaselineCompareReasonCode::EvidenceCaptureIncomplete,
                default => BaselineCompareReasonCode::NoDriftDetected,
            };
        }

        $updatedContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $updatedContext['baseline_compare'] = array_merge(
            is_array($updatedContext['baseline_compare'] ?? null) ? $updatedContext['baseline_compare'] : [],
            [
                'inventory_sync_run_id' => (int) $inventorySyncRun->getKey(),
                'since' => $since?->toIso8601String(),
                'subjects_total' => $subjectsTotal,
                'strategy' => $this->strategyContext(
                    strategySelection: $strategySelection,
                    executionDiagnostics: $strategyDiagnostics,
                    stateCounts: $strategyStateCounts,
                ),
                'evidence_capture' => $phaseStats,
                'evidence_gaps' => [
                    'count' => $gapsCount,
                    'by_reason' => $gapsByReason,
                    ...$gapsByReason,
                    'subjects' => $gapSubjects !== [] ? $gapSubjects : null,
                ],
                'resume_token' => $resumeToken,
                'coverage' => [
                    'effective_types' => $effectiveTypes,
                    'covered_types' => $coveredTypes,
                    'uncovered_types' => $uncoveredTypes,
                    'proof' => true,
                    ...$coverageBreakdown,
                    ...$baselineCoverage,
                ],
                'rbac_role_definitions' => $rbacRoleDefinitionSummary,
                'fidelity' => $overallFidelity,
                'reason_code' => $reasonCode?->value,
            ],
        );
        $updatedContext['findings'] = array_merge(
            is_array($updatedContext['findings'] ?? null) ? $updatedContext['findings'] : [],
            [
                'counts_by_change_type' => $countsByChangeType,
            ],
        );
        $updatedContext['progress'] = array_merge(
            is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
            [
                'phase' => [
                    'key' => 'finalizing',
                    'label' => 'Finalizing baseline comparison.',
                ],
            ],
        );
        $updatedContext['result'] = [
            'findings_total' => count($driftResults),
            'findings_upserted' => (int) $upsertResult['processed_count'],
            'findings_resolved' => $resolvedCount,
            'severity_breakdown' => $severityBreakdown,
        ];
        $updatedContext = $this->withCompareReasonTranslation(
            $updatedContext,
            $reasonCode?->value,
        );
        $this->operationRun->update(['context' => $updatedContext]);

        $this->auditCompleted(
            auditLogger: $auditLogger,
            tenant: $tenant,
            profile: $profile,
            initiator: $initiator,
            captureMode: $captureMode,
            subjectsTotal: $subjectsTotal,
            evidenceCaptureStats: $phaseStats,
            gaps: [
                'count' => $gapsCount,
                'by_reason' => $gapsByReason,
            ],
            summaryCounts: $summaryCounts,
        );
    }

    /**
     * Baseline hashes depend on which evidence fidelity was available at capture time (content vs meta).
     *
     * Current state evidence is therefore selected to be comparable to the baseline hash:
     * - If baseline evidence fidelity is meta: force meta evidence for current (inventory meta contract).
     * - If baseline evidence fidelity is content: require current content evidence (since-rule); otherwise treat as a gap.
     *
     * @param  array<string, array{subject_type: string, subject_external_id: string, policy_type: string, baseline_hash: string, meta_jsonb: array<string, mixed>}>  $baselineItems
     * @param  array<string, array{subject_external_id: string, policy_type: string, meta_jsonb: array<string, mixed>}>  $currentItems
     * @param  array<string, ResolvedEvidence|null>  $resolvedBestEvidence
     * @param  array<string, ResolvedEvidence>  $resolvedMetaEvidence
     * @return array<string, ResolvedEvidence|null>
     */
    private function resolveEffectiveCurrentEvidence(
        array $baselineItems,
        array $currentItems,
        array $resolvedBestEvidence,
        array $resolvedMetaEvidence,
    ): array {
        /**
         * @var array<string, string>
         */
        $baselineFidelityByKey = [];

        foreach ($baselineItems as $key => $baselineItem) {
            $provenance = $this->baselineProvenanceFromMetaJsonb($baselineItem['meta_jsonb'] ?? []);
            $baselineFidelityByKey[$key] = (string) ($provenance['fidelity'] ?? EvidenceProvenance::FidelityMeta);
        }

        $effective = [];

        foreach ($currentItems as $key => $currentItem) {
            if (array_key_exists($key, $baselineItems)) {
                $baselineFidelity = $baselineFidelityByKey[$key] ?? EvidenceProvenance::FidelityMeta;

                if ($baselineFidelity === EvidenceProvenance::FidelityMeta) {
                    $effective[$key] = $resolvedMetaEvidence[$key] ?? null;

                    continue;
                }

                $best = $resolvedBestEvidence[$key] ?? null;

                if ($best instanceof ResolvedEvidence && $best->fidelity === EvidenceProvenance::FidelityContent) {
                    $effective[$key] = $best;
                } else {
                    $effective[$key] = null;
                }

                continue;
            }

            $effective[$key] = $resolvedBestEvidence[$key] ?? null;
        }

        return $effective;
    }

    /**
     * Rekey resolved evidence from "policy_type|external_id" to the current items key ("policy_type|subject_key").
     *
     * @param  array<string, array{subject_external_id: string, policy_type: string}>  $currentItems
     * @param  array<string, ResolvedEvidence|null>  $resolvedByExternalId
     * @return array<string, ResolvedEvidence|null>
     */
    private function rekeyResolvedEvidenceBySubjectKey(array $currentItems, array $resolvedByExternalId): array
    {
        $rekeyed = [];

        foreach ($currentItems as $key => $currentItem) {
            $policyType = (string) ($currentItem['policy_type'] ?? '');
            $externalId = (string) ($currentItem['subject_external_id'] ?? '');

            if ($policyType === '' || $externalId === '') {
                $rekeyed[$key] = null;

                continue;
            }

            $resolvedKey = $policyType.'|'.$externalId;
            $rekeyed[$key] = $resolvedByExternalId[$resolvedKey] ?? null;
        }

        return $rekeyed;
    }

    /**
     * @param  array{
     *   captured_versions?: array<string, array{
     *     policy_type: string,
     *     subject_external_id: string,
     *     version: \App\Models\PolicyVersion,
     *     observed_at: string,
     *     observed_operation_run_id: ?int
     *   }>
     * }  $phaseResult
     * @return array<string, ResolvedEvidence>
     */
    private function resolveCapturedCurrentEvidenceByExternalId(array $phaseResult): array
    {
        $capturedVersions = is_array($phaseResult['captured_versions'] ?? null)
            ? $phaseResult['captured_versions']
            : [];

        if ($capturedVersions === []) {
            return [];
        }

        $contentEvidenceProvider = app(ContentEvidenceProvider::class);
        $resolved = [];

        foreach ($capturedVersions as $key => $capturedVersion) {
            $version = $capturedVersion['version'] ?? null;
            $subjectExternalId = trim((string) ($capturedVersion['subject_external_id'] ?? ''));
            $observedAt = $capturedVersion['observed_at'] ?? null;
            $observedAt = is_string($observedAt) && $observedAt !== ''
                ? CarbonImmutable::parse($observedAt)
                : null;
            $observedOperationRunId = $capturedVersion['observed_operation_run_id'] ?? null;
            $observedOperationRunId = is_numeric($observedOperationRunId) ? (int) $observedOperationRunId : null;

            if (! $version instanceof \App\Models\PolicyVersion || $subjectExternalId === '' || ! is_string($key) || $key === '') {
                continue;
            }

            $resolved[$key] = $contentEvidenceProvider->fromPolicyVersion(
                version: $version,
                subjectExternalId: $subjectExternalId,
                observedAt: $observedAt,
                observedOperationRunId: $observedOperationRunId,
            );
        }

        return $resolved;
    }


    private function completeWithCoverageWarning(
        OperationRunService $operationRunService,
        AuditLogger $auditLogger,
        ManagedEnvironment $tenant,
        BaselineProfile $profile,
        ?User $initiator,
        ?OperationRun $inventorySyncRun,
        bool $coverageProof,
        array $effectiveTypes,
        array $coveredTypes,
        array $uncoveredTypes,
        int $errorsRecorded,
        BaselineCaptureMode $captureMode,
        BaselineCompareReasonCode $reasonCode = BaselineCompareReasonCode::CoverageUnproven,
        ?array $evidenceGapsByReason = null,
    ): void {
        $summaryCounts = [
            'total' => 0,
            'processed' => 0,
            'succeeded' => 0,
            'failed' => 0,
            'errors_recorded' => max(1, $errorsRecorded),
            'high' => 0,
            'medium' => 0,
            'low' => 0,
            'findings_created' => 0,
            'findings_reopened' => 0,
            'findings_unchanged' => 0,
        ];

        $operationRunService->updateRun(
            $this->operationRun,
            status: OperationRunStatus::Completed->value,
            outcome: OperationRunOutcome::PartiallySucceeded->value,
            summaryCounts: $summaryCounts,
        );

        $updatedContext = is_array($this->operationRun->context) ? $this->operationRun->context : [];
        $evidenceCapture = [
            'requested' => 0,
            'succeeded' => 0,
            'skipped' => 0,
            'failed' => 0,
            'throttled' => 0,
        ];

        $evidenceGapsByReason ??= [
            BaselineCompareReasonCode::CoverageUnproven->value => max(1, $errorsRecorded),
        ];

        $updatedContext['baseline_compare'] = array_merge(
            is_array($updatedContext['baseline_compare'] ?? null) ? $updatedContext['baseline_compare'] : [],
            [
                'inventory_sync_run_id' => $inventorySyncRun instanceof OperationRun ? (int) $inventorySyncRun->getKey() : null,
                'subjects_total' => 0,
                'evidence_capture' => $evidenceCapture,
                'evidence_gaps' => [
                    'count' => array_sum($evidenceGapsByReason),
                    'by_reason' => $evidenceGapsByReason,
                    ...$evidenceGapsByReason,
                ],
                'resume_token' => null,
                'reason_code' => $reasonCode->value,
                'coverage' => [
                    'effective_types' => array_values($effectiveTypes),
                    'covered_types' => array_values($coveredTypes),
                    'uncovered_types' => array_values($uncoveredTypes),
                    'proof' => $coverageProof,
                    'subjects_total' => 0,
                    'resolved_total' => 0,
                    'resolved_content' => 0,
                    'resolved_meta' => 0,
                    'policy_types_content' => [],
                    'policy_types_meta_only' => [],
                ],
                'rbac_role_definitions' => $this->emptyRbacRoleDefinitionSummary(),
                'fidelity' => 'meta',
            ],
        );
        $updatedContext['findings'] = array_merge(
            is_array($updatedContext['findings'] ?? null) ? $updatedContext['findings'] : [],
            [
                'counts_by_change_type' => [],
            ],
        );
        $updatedContext['progress'] = array_merge(
            is_array($updatedContext['progress'] ?? null) ? $updatedContext['progress'] : [],
            [
                'phase' => [
                    'key' => 'finalizing',
                    'label' => 'Finalizing baseline comparison.',
                ],
            ],
        );
        $updatedContext['result'] = [
            'findings_total' => 0,
            'findings_upserted' => 0,
            'findings_resolved' => 0,
            'severity_breakdown' => [],
        ];
        $updatedContext = $this->withCompareReasonTranslation($updatedContext, $reasonCode->value);

        $this->operationRun->update(['context' => $updatedContext]);

        $this->auditCompleted(
            auditLogger: $auditLogger,
            tenant: $tenant,
            profile: $profile,
            initiator: $initiator,
            captureMode: $captureMode,
            subjectsTotal: 0,
            evidenceCaptureStats: $evidenceCapture,
            gaps: [
                'count' => array_sum($evidenceGapsByReason),
                'by_reason' => $evidenceGapsByReason,
            ],
            summaryCounts: $summaryCounts,
        );
    }

    /**
     * Load baseline snapshot items keyed by "policy_type|subject_key".
     *
     * @return array{
     *   items: array<string, array{subject_type: string, subject_external_id: string, subject_key: string, policy_type: string, baseline_hash: string, meta_jsonb: array<string, mixed>}>,
     *   gaps: array<string, int>,
     *   ambiguous_keys: list<string>
     * }
     */
    private function loadBaselineItems(int $snapshotId, array $policyTypes): array
    {
        $items = [];
        $gaps = [];

        /**
         * @var array<string, true>
         */
        $ambiguousKeys = [];

        if ($policyTypes === []) {
            return [
                'items' => $items,
                'gaps' => $gaps,
                'ambiguous_keys' => [],
            ];
        }

        $query = BaselineSnapshotItem::query()
            ->where('baseline_snapshot_id', $snapshotId);

        $query->whereIn('policy_type', $policyTypes);

        $query
            ->orderBy('id')
            ->chunk(500, function ($snapshotItems) use (&$items, &$gaps, &$ambiguousKeys): void {
                foreach ($snapshotItems as $item) {
                    $metaJsonb = is_array($item->meta_jsonb) ? $item->meta_jsonb : [];
                    $displayName = $metaJsonb['display_name'] ?? ($metaJsonb['displayName'] ?? null);
                    $displayName = is_string($displayName) ? $displayName : null;

                    $subjectKey = $this->normalizeSubjectKey(
                        policyType: (string) $item->policy_type,
                        storedSubjectKey: is_string($item->subject_key) ? $item->subject_key : null,
                        displayName: $displayName,
                    );

                    if ($subjectKey === '') {
                        $gaps['missing_subject_key_baseline'] = ($gaps['missing_subject_key_baseline'] ?? 0) + 1;

                        continue;
                    }

                    $key = $item->policy_type.'|'.$subjectKey;

                    if (array_key_exists($key, $ambiguousKeys)) {
                        continue;
                    }

                    if (array_key_exists($key, $items)) {
                        $ambiguousKeys[$key] = true;
                        unset($items[$key]);

                        $gaps['ambiguous_match'] = ($gaps['ambiguous_match'] ?? 0) + 1;

                        continue;
                    }

                    $items[$key] = [
                        'subject_type' => (string) $item->subject_type,
                        'subject_external_id' => (string) $item->subject_external_id,
                        'subject_key' => $subjectKey,
                        'policy_type' => (string) $item->policy_type,
                        'baseline_hash' => (string) $item->baseline_hash,
                        'meta_jsonb' => $metaJsonb,
                    ];
                }
            });

        ksort($gaps);

        return [
            'items' => $items,
            'gaps' => $gaps,
            'ambiguous_keys' => array_values(array_keys($ambiguousKeys)),
        ];
    }

    /**
     * @param  array<string, mixed>  $context
     * @return array<string, mixed>
     */
    private function withCompareReasonTranslation(array $context, ?string $reasonCode): array
    {
        if (! is_string($reasonCode) || trim($reasonCode) === '') {
            unset($context['reason_translation'], $context['next_steps']);

            return $context;
        }

        $translation = app(ReasonPresenter::class)->forArtifactTruth($reasonCode, 'baseline_compare');

        if ($translation === null) {
            return $context;
        }

        $context['reason_translation'] = $translation->toArray();
        $context['reason_code'] = $reasonCode;

        if ($translation->toLegacyNextSteps() !== []) {
            $context['next_steps'] = $translation->toLegacyNextSteps();
        }

        return $context;
    }

    /**
     * @param  array<string, mixed>  $context
     * @return array<string, mixed>
     */
    private function withCompareStrategySelection(array $context, CompareStrategySelection $strategySelection): array
    {
        $context['baseline_compare'] = array_merge(
            is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [],
            [
                'strategy' => $this->strategyContext($strategySelection),
            ],
        );

        return $context;
    }

    /**
     * @param  array<string, mixed>  $context
     * @param  array<string, mixed>  $executionDiagnostics
     * @return array<string, mixed>
     */
    private function withCompareStrategyDiagnostics(
        array $context,
        CompareStrategySelection $strategySelection,
        array $executionDiagnostics,
        array $stateCounts = [],
    ): array {
        $context['baseline_compare'] = array_merge(
            is_array($context['baseline_compare'] ?? null) ? $context['baseline_compare'] : [],
            [
                'strategy' => $this->strategyContext($strategySelection, $executionDiagnostics, $stateCounts),
            ],
        );

        return $context;
    }

    /**
     * @param  array<string, mixed>  $executionDiagnostics
     * @param  array<string, int>  $stateCounts
     * @return array{
     *   key: ?string,
     *   selection_state: string,
     *   matched_scope_entries: list<array<string, mixed>>,
     *   rejected_scope_entries: list<array<string, mixed>>,
     *   operator_reason: string,
     *   diagnostics: array<string, mixed>,
     *   execution_diagnostics: array<string, mixed>,
     *   state_counts: array<string, int>
     * }
     */
    private function strategyContext(
        CompareStrategySelection $strategySelection,
        array $executionDiagnostics = [],
        array $stateCounts = [],
    ): array {
        return [
            'key' => $strategySelection->strategyKey?->value,
            'selection_state' => $strategySelection->selectionState->value,
            'matched_scope_entries' => $strategySelection->matchedScopeEntries,
            'rejected_scope_entries' => $strategySelection->rejectedScopeEntries,
            'operator_reason' => $strategySelection->operatorReason,
            'diagnostics' => $strategySelection->diagnostics,
            'execution_diagnostics' => $executionDiagnostics,
            'state_counts' => $stateCounts,
        ];
    }

    private function strategySelectionGapReason(CompareStrategySelection $strategySelection): string
    {
        return $strategySelection->selectionState === StrategySelectionState::Mixed
            ? 'mixed_scope'
            : 'unsupported_subjects';
    }

    /**
     * @param  mixed  $subjectResults
     * @return array{
     *   drift_results: array<int, array{change_type: string, severity: string, evidence_fidelity: string, subject_type: string, subject_external_id: string, subject_key: string, policy_type: string, baseline_hash: string, current_hash: string, evidence: array<string, mixed>}>,
     *   gap_counts: array<string, int>,
     *   gap_subjects: list<array<string, mixed>>,
     *   state_counts: array<string, int>
     * }
     */
    private function normalizeStrategySubjectResults(mixed $subjectResults): array
    {
        if (! is_array($subjectResults)) {
            return [
                'drift_results' => [],
                'gap_counts' => [],
                'gap_subjects' => [],
                'state_counts' => [],
            ];
        }

        $driftResults = [];
        $gapCounts = [];
        $gapSubjects = [];
        $stateCounts = [];

        foreach ($subjectResults as $subjectResult) {
            if (! $subjectResult instanceof CompareSubjectResult) {
                continue;
            }

            $state = $subjectResult->compareState->value;
            $stateCounts[$state] = ($stateCounts[$state] ?? 0) + 1;

            if ($subjectResult->compareState === CompareState::Drift && $subjectResult->findingCandidate instanceof CompareFindingCandidate) {
                $driftResults[] = [
                    'change_type' => $subjectResult->findingCandidate->changeType,
                    'severity' => $subjectResult->findingCandidate->severity,
                    'subject_type' => $subjectResult->projection->platformSubjectClass,
                    'subject_external_id' => $subjectResult->subjectIdentity->externalSubjectId ?? '',
                    'subject_key' => $subjectResult->subjectIdentity->subjectKey,
                    'policy_type' => $subjectResult->subjectIdentity->subjectTypeKey,
                    'evidence_fidelity' => $subjectResult->evidenceQuality,
                    'baseline_hash' => is_string(data_get($subjectResult->findingCandidate->evidencePayload, 'baseline.hash'))
                        ? (string) data_get($subjectResult->findingCandidate->evidencePayload, 'baseline.hash')
                        : '',
                    'current_hash' => is_string(data_get($subjectResult->findingCandidate->evidencePayload, 'current.hash'))
                        ? (string) data_get($subjectResult->findingCandidate->evidencePayload, 'current.hash')
                        : '',
                    'evidence' => $subjectResult->findingCandidate->evidencePayload,
                ];

                continue;
            }

            if (! $subjectResult->isGapState()) {
                continue;
            }

            $reasonCode = $subjectResult->gapReasonCode() ?? $this->defaultGapReasonForState($subjectResult->compareState);
            $gapCounts[$reasonCode] = ($gapCounts[$reasonCode] ?? 0) + 1;
            $gapSubjects[] = $subjectResult->gapRecord() ?? $this->fallbackGapRecord($subjectResult, $reasonCode);
        }

        ksort($gapCounts);
        ksort($stateCounts);

        return [
            'drift_results' => $driftResults,
            'gap_counts' => $gapCounts,
            'gap_subjects' => $gapSubjects,
            'state_counts' => $stateCounts,
        ];
    }

    private function defaultGapReasonForState(CompareState $state): string
    {
        return match ($state) {
            CompareState::Unsupported => 'unsupported_subject',
            CompareState::Ambiguous => 'ambiguous_match',
            CompareState::Failed => 'strategy_failed',
            default => 'missing_current',
        };
    }

    /**
     * @return array<string, mixed>
     */
    private function fallbackGapRecord(CompareSubjectResult $subjectResult, string $reasonCode): array
    {
        $descriptor = $this->subjectResolver()->describeForCompare(
            policyType: $subjectResult->subjectIdentity->subjectTypeKey,
            subjectExternalId: $subjectResult->subjectIdentity->externalSubjectId,
            subjectKey: $subjectResult->subjectIdentity->subjectKey,
        );

        $outcome = match ($reasonCode) {
            'missing_current' => $this->subjectResolver()->missingExpectedRecord($descriptor),
            'ambiguous_match' => $this->subjectResolver()->ambiguousMatch($descriptor),
            default => $this->subjectResolver()->captureFailed($descriptor),
        };

        return array_merge($descriptor->toArray(), $outcome->toArray(), [
            'reason_code' => $reasonCode,
            'search_text' => strtolower(implode(' ', array_filter([
                $subjectResult->subjectIdentity->subjectTypeKey,
                $subjectResult->subjectIdentity->subjectKey,
                $reasonCode,
                $subjectResult->projection->operatorLabel,
            ]))),
        ]);
    }

    /**
     * Load current inventory items keyed by "policy_type|subject_key".
     *
     * @return array{
     *   items: array<string, array{subject_external_id: string, subject_key: string, policy_type: string, meta_jsonb: array<string, mixed>}>,
     *   gaps: array<string, int>,
     *   ambiguous_keys: list<string>
     * }
     */
    private function loadCurrentInventory(
        ManagedEnvironment $tenant,
        array $policyTypes,
        ?int $latestInventorySyncRunId = null,
    ): array {
        $query = InventoryItem::query()
            ->where('managed_environment_id', $tenant->getKey());

        if (is_int($latestInventorySyncRunId) && $latestInventorySyncRunId > 0) {
            $query->where('last_seen_operation_run_id', $latestInventorySyncRunId);
        }

        if ($policyTypes === []) {
            return [
                'items' => [],
                'gaps' => [],
                'ambiguous_keys' => [],
            ];
        }

        $query->whereIn('policy_type', $policyTypes);

        $items = [];
        $gaps = [];

        /**
         * @var array<string, true>
         */
        $ambiguousKeys = [];

        $query->orderBy('policy_type')
            ->orderBy('external_id')
            ->chunk(500, function ($inventoryItems) use (&$items, &$gaps, &$ambiguousKeys): void {
                foreach ($inventoryItems as $inventoryItem) {
                    $metaJsonb = is_array($inventoryItem->meta_jsonb) ? $inventoryItem->meta_jsonb : [];
                    $subjectKey = $this->normalizeSubjectKey(
                        policyType: (string) $inventoryItem->policy_type,
                        displayName: is_string($inventoryItem->display_name) ? $inventoryItem->display_name : null,
                        subjectExternalId: is_string($inventoryItem->external_id) ? $inventoryItem->external_id : null,
                    );

                    if ($subjectKey === '') {
                        $gaps['missing_subject_key_current'] = ($gaps['missing_subject_key_current'] ?? 0) + 1;

                        continue;
                    }

                    $key = $inventoryItem->policy_type.'|'.$subjectKey;

                    if (array_key_exists($key, $ambiguousKeys)) {
                        continue;
                    }

                    if (array_key_exists($key, $items)) {
                        $ambiguousKeys[$key] = true;
                        unset($items[$key]);

                        $gaps['ambiguous_match'] = ($gaps['ambiguous_match'] ?? 0) + 1;

                        continue;
                    }

                    $items[$key] = [
                        'subject_external_id' => (string) $inventoryItem->external_id,
                        'subject_key' => $subjectKey,
                        'policy_type' => (string) $inventoryItem->policy_type,
                        'meta_jsonb' => array_replace($metaJsonb, [
                            'display_name' => $metaJsonb['display_name'] ?? $inventoryItem->display_name,
                            'category' => $metaJsonb['category'] ?? $inventoryItem->category,
                            'platform' => $metaJsonb['platform'] ?? $inventoryItem->platform,
                            'is_built_in' => $metaJsonb['is_built_in'] ?? null,
                            'role_permission_count' => $metaJsonb['role_permission_count'] ?? null,
                        ]),
                    ];
                }
            });

        ksort($gaps);

        return [
            'items' => $items,
            'gaps' => $gaps,
            'ambiguous_keys' => array_values(array_keys($ambiguousKeys)),
        ];
    }

    private function resolveLatestInventorySyncRun(ManagedEnvironment $tenant): ?OperationRun
    {
        $run = OperationRun::query()
            ->where('managed_environment_id', (int) $tenant->getKey())
            ->where('type', OperationRunType::InventorySync->value)
            ->where('status', OperationRunStatus::Completed->value)
            ->orderByDesc('completed_at')
            ->orderByDesc('id')
            ->first();

        return $run instanceof OperationRun ? $run : null;
    }

    private function snapshotBlockedMessage(string $reasonCode): string
    {
        return match ($reasonCode) {
            BaselineReasonCodes::COMPARE_SNAPSHOT_BUILDING => 'The selected baseline snapshot is still building and cannot be used for compare yet.',
            BaselineReasonCodes::COMPARE_SNAPSHOT_INCOMPLETE => 'The selected baseline snapshot is incomplete and cannot be used for compare.',
            BaselineReasonCodes::COMPARE_SNAPSHOT_SUPERSEDED => 'A newer complete baseline snapshot is now current, so this historical snapshot is blocked from compare.',
            BaselineReasonCodes::COMPARE_INVALID_SNAPSHOT => 'The selected baseline snapshot is no longer available.',
            default => 'No consumable baseline snapshot is currently available for compare.',
        };
    }

    /**
     * @param  array<string, mixed>  $context
     * @return list<string>
     */
    private function truthfulTypesFromContext(array $context, BaselineScope $effectiveScope): array
    {
        $truthfulTypes = data_get($context, 'effective_scope.truthful_types');

        if (is_array($truthfulTypes)) {
            $truthfulTypes = array_values(array_filter($truthfulTypes, 'is_string'));

            if ($truthfulTypes !== []) {
                sort($truthfulTypes, SORT_STRING);

                return $truthfulTypes;
            }
        }

        return $effectiveScope->allTypes();
    }

    private function normalizeSubjectKey(
        string $policyType,
        ?string $storedSubjectKey = null,
        ?string $displayName = null,
        ?string $subjectExternalId = null,
    ): string {
        $storedSubjectKey = is_string($storedSubjectKey) ? trim(mb_strtolower($storedSubjectKey)) : '';

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

        return BaselineSubjectKey::forPolicy($policyType, $displayName, $subjectExternalId) ?? '';
    }

    /**
     * @return array{total_compared: int, unchanged: int, modified: int, missing: int, unexpected: int}
     */
    private function emptyRbacRoleDefinitionSummary(): array
    {
        return [
            'total_compared' => 0,
            'unchanged' => 0,
            'modified' => 0,
            'missing' => 0,
            'unexpected' => 0,
        ];
    }

    /**
     * @param  array<string, int>  ...$gaps
     * @return array<string, int>
     */
    private function mergeGapCounts(array ...$gaps): array
    {
        $merged = [];

        foreach ($gaps as $gap) {
            foreach ($gap as $reason => $count) {
                if (! is_string($reason) || ! is_numeric($count)) {
                    continue;
                }

                $count = (int) $count;

                if ($count <= 0) {
                    continue;
                }

                $merged[$reason] = ($merged[$reason] ?? 0) + $count;
            }
        }

        ksort($merged);

        return $merged;
    }

    private const GAP_SUBJECTS_LIMIT = 50;

    /**
     * @param  list<string>  $ambiguousKeys
     * @return list<array<string, mixed>>
     */
    private function collectGapSubjects(array $ambiguousKeys, mixed $phaseGapSubjects, mixed $driftGapSubjects): array
    {
        $subjects = [];
        $seen = [];

        if ($ambiguousKeys !== []) {
            foreach (array_slice($ambiguousKeys, 0, self::GAP_SUBJECTS_LIMIT) as $ambiguousKey) {
                if (! is_string($ambiguousKey) || $ambiguousKey === '') {
                    continue;
                }

                [$policyType, $subjectKey] = $this->splitGapSubjectKey($ambiguousKey);

                if ($policyType === null || $subjectKey === null) {
                    continue;
                }

                $descriptor = $this->subjectResolver()->describeForCompare($policyType, subjectKey: $subjectKey);
                $record = array_merge($descriptor->toArray(), $this->subjectResolver()->ambiguousMatch($descriptor)->toArray());
                $fingerprint = md5(json_encode([$record['policy_type'], $record['subject_key'], $record['reason_code']]));

                if (isset($seen[$fingerprint])) {
                    continue;
                }

                $seen[$fingerprint] = true;
                $subjects[] = $record;
            }
        }

        foreach ($this->normalizeStructuredGapSubjects($phaseGapSubjects) as $record) {
            $fingerprint = md5(json_encode([$record['policy_type'] ?? null, $record['subject_key'] ?? null, $record['reason_code'] ?? null]));

            if (isset($seen[$fingerprint])) {
                continue;
            }

            $seen[$fingerprint] = true;
            $subjects[] = $record;
        }

        foreach ($this->normalizeLegacyGapSubjects($driftGapSubjects) as $record) {
            $fingerprint = md5(json_encode([$record['policy_type'] ?? null, $record['subject_key'] ?? null, $record['reason_code'] ?? null]));

            if (isset($seen[$fingerprint])) {
                continue;
            }

            $seen[$fingerprint] = true;
            $subjects[] = $record;
        }

        return array_slice($subjects, 0, self::GAP_SUBJECTS_LIMIT);
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function normalizeStructuredGapSubjects(mixed $value): array
    {
        if (! is_array($value)) {
            return [];
        }

        $subjects = [];

        foreach ($value as $record) {
            if (! is_array($record)) {
                continue;
            }

            if (! is_string($record['policy_type'] ?? null) || ! is_string($record['subject_key'] ?? null) || ! is_string($record['reason_code'] ?? null)) {
                continue;
            }

            $subjects[] = $record;
        }

        return $subjects;
    }

    /**
     * @return list<array<string, mixed>>
     */
    private function normalizeLegacyGapSubjects(mixed $value): array
    {
        if (! is_array($value)) {
            return [];
        }

        $subjects = [];

        foreach ($value as $reasonCode => $keys) {
            if (! is_string($reasonCode) || ! is_array($keys)) {
                continue;
            }

            foreach ($keys as $key) {
                if (! is_string($key) || $key === '') {
                    continue;
                }

                [$policyType, $subjectKey] = $this->splitGapSubjectKey($key);

                if ($policyType === null || $subjectKey === null) {
                    continue;
                }

                $descriptor = $this->subjectResolver()->describeForCompare($policyType, subjectKey: $subjectKey);
                $outcome = match ($reasonCode) {
                    'missing_current' => $this->subjectResolver()->missingExpectedRecord($descriptor),
                    'ambiguous_match' => $this->subjectResolver()->ambiguousMatch($descriptor),
                    default => $this->subjectResolver()->captureFailed($descriptor),
                };

                $record = array_merge($descriptor->toArray(), $outcome->toArray());
                $record['reason_code'] = $reasonCode;
                $subjects[] = $record;
            }
        }

        return $subjects;
    }

    /**
     * @return array{0: ?string, 1: ?string}
     */
    private function splitGapSubjectKey(string $value): array
    {
        $parts = explode('|', $value, 2);

        if (count($parts) !== 2) {
            return [null, null];
        }

        [$policyType, $subjectKey] = $parts;
        $policyType = trim($policyType);
        $subjectKey = trim($subjectKey);

        if ($policyType === '' || $subjectKey === '') {
            return [null, null];
        }

        return [$policyType, $subjectKey];
    }

    private function subjectResolver(): SubjectResolver
    {
        return app(SubjectResolver::class);
    }

    /**
     * @param  array<string, array{subject_external_id: string, policy_type: string, meta_jsonb: array<string, mixed>}>  $currentItems
     * @param  array<string, ResolvedEvidence|null>  $resolvedCurrentEvidence
     * @return array{
     *   subjects_total: int,
     *   resolved_total: int,
     *   resolved_content: int,
     *   resolved_meta: int,
     *   policy_types_content: list<string>,
     *   policy_types_meta_only: list<string>
     * }
     */
    private function summarizeCurrentEvidenceCoverage(array $currentItems, array $resolvedCurrentEvidence): array
    {
        $subjectsTotal = count($currentItems);
        $resolvedContent = 0;
        $resolvedMeta = 0;

        /**
         * @var array<string, array{resolved_content: int, resolved_meta: int}>
         */
        $resolvedByType = [];

        foreach ($currentItems as $key => $item) {
            $type = (string) ($item['policy_type'] ?? '');

            if ($type === '') {
                continue;
            }

            $resolvedByType[$type] ??= ['resolved_content' => 0, 'resolved_meta' => 0];

            $evidence = $resolvedCurrentEvidence[$key] ?? null;

            if (! $evidence instanceof ResolvedEvidence) {
                continue;
            }

            if ($evidence->fidelity === EvidenceProvenance::FidelityContent) {
                $resolvedContent++;
                $resolvedByType[$type]['resolved_content']++;
            } else {
                $resolvedMeta++;
                $resolvedByType[$type]['resolved_meta']++;
            }
        }

        $policyTypesContent = [];
        $policyTypesMetaOnly = [];

        foreach ($resolvedByType as $policyType => $counts) {
            if ($counts['resolved_content'] > 0) {
                $policyTypesContent[] = $policyType;

                continue;
            }

            if ($counts['resolved_meta'] > 0) {
                $policyTypesMetaOnly[] = $policyType;
            }
        }

        sort($policyTypesContent, SORT_STRING);
        sort($policyTypesMetaOnly, SORT_STRING);

        return [
            'subjects_total' => $subjectsTotal,
            'resolved_total' => $resolvedContent + $resolvedMeta,
            'resolved_content' => $resolvedContent,
            'resolved_meta' => $resolvedMeta,
            'policy_types_content' => $policyTypesContent,
            'policy_types_meta_only' => $policyTypesMetaOnly,
        ];
    }

    /**
     * @param  array<string, array{subject_type: string, subject_external_id: string, policy_type: string, baseline_hash: string, meta_jsonb: array<string, mixed>}>  $baselineItems
     * @return array{
     *   baseline_total: int,
     *   baseline_content: int,
     *   baseline_meta: int,
     *   baseline_policy_types_content: list<string>,
     *   baseline_policy_types_meta_only: list<string>
     * }
     */
    private function summarizeBaselineEvidenceCoverage(array $baselineItems): array
    {
        $baselineTotal = count($baselineItems);
        $baselineContent = 0;
        $baselineMeta = 0;

        /**
         * @var array<string, array{baseline_content: int, baseline_meta: int}>
         */
        $countsByType = [];

        foreach ($baselineItems as $key => $item) {
            $type = (string) ($item['policy_type'] ?? '');

            if ($type === '') {
                continue;
            }

            $countsByType[$type] ??= ['baseline_content' => 0, 'baseline_meta' => 0];

            $provenance = $this->baselineProvenanceFromMetaJsonb($item['meta_jsonb'] ?? []);
            $fidelity = (string) ($provenance['fidelity'] ?? EvidenceProvenance::FidelityMeta);

            if ($fidelity === EvidenceProvenance::FidelityContent) {
                $baselineContent++;
                $countsByType[$type]['baseline_content']++;
            } else {
                $baselineMeta++;
                $countsByType[$type]['baseline_meta']++;
            }
        }

        $policyTypesContent = [];
        $policyTypesMetaOnly = [];

        foreach ($countsByType as $policyType => $counts) {
            if (($counts['baseline_content'] ?? 0) > 0) {
                $policyTypesContent[] = $policyType;

                continue;
            }

            if (($counts['baseline_meta'] ?? 0) > 0) {
                $policyTypesMetaOnly[] = $policyType;
            }
        }

        sort($policyTypesContent, SORT_STRING);
        sort($policyTypesMetaOnly, SORT_STRING);

        return [
            'baseline_total' => $baselineTotal,
            'baseline_content' => $baselineContent,
            'baseline_meta' => $baselineMeta,
            'baseline_policy_types_content' => $policyTypesContent,
            'baseline_policy_types_meta_only' => $policyTypesMetaOnly,
        ];
    }

    /**
     * @param  array<string, mixed>  $metaJsonb
     * @return array{fidelity: string, source: string, observed_at: ?string, observed_operation_run_id: ?int}
     */
    private function baselineProvenanceFromMetaJsonb(array $metaJsonb): array
    {
        $evidence = $metaJsonb;

        if (is_array($metaJsonb['evidence'] ?? null)) {
            $evidence = $metaJsonb['evidence'];
        }

        $fidelity = $evidence['fidelity'] ?? EvidenceProvenance::FidelityMeta;
        $fidelity = is_string($fidelity) ? strtolower(trim($fidelity)) : EvidenceProvenance::FidelityMeta;
        if (! EvidenceProvenance::isValidFidelity($fidelity)) {
            $fidelity = EvidenceProvenance::FidelityMeta;
        }

        $source = $evidence['source'] ?? EvidenceProvenance::SourceInventory;
        $source = is_string($source) ? strtolower(trim($source)) : EvidenceProvenance::SourceInventory;
        if (! EvidenceProvenance::isValidSource($source)) {
            $source = EvidenceProvenance::SourceInventory;
        }

        $observedAt = $evidence['observed_at'] ?? null;
        $observedAt = is_string($observedAt) ? trim($observedAt) : null;

        $observedAtCarbon = null;
        if (is_string($observedAt) && $observedAt !== '') {
            try {
                $observedAtCarbon = CarbonImmutable::parse($observedAt);
            } catch (\Throwable) {
                $observedAtCarbon = null;
            }
        }

        $observedOperationRunId = $evidence['observed_operation_run_id'] ?? null;
        $observedOperationRunId = is_numeric($observedOperationRunId) ? (int) $observedOperationRunId : null;

        return EvidenceProvenance::build(
            fidelity: $fidelity,
            source: $source,
            observedAt: $observedAtCarbon,
            observedOperationRunId: $observedOperationRunId,
        );
    }

    /**
     * Upsert drift findings using stable fingerprints.
     *
     * @param  array<int, array{change_type: string, severity: string, subject_type: string, subject_external_id: string, subject_key: string, policy_type: string, baseline_hash: string, current_hash: string, evidence: array<string, mixed>}>  $driftResults
     * @return array{processed_count: int, created_count: int, reopened_count: int, unchanged_count: int, seen_fingerprints: array<int, string>}
     */
    private function upsertFindings(
        ManagedEnvironment $tenant,
        BaselineProfile $profile,
        string $scopeKey,
        array $driftResults,
    ): array {
        $tenantId = (int) $tenant->getKey();
        $baselineProfileId = (int) $profile->getKey();
        $observedAt = CarbonImmutable::now();
        $processedCount = 0;
        $createdCount = 0;
        $reopenedCount = 0;
        $unchangedCount = 0;
        $seenFingerprints = [];
        $slaPolicy = app(FindingSlaPolicy::class);

        foreach ($driftResults as $driftItem) {
            $subjectKey = (string) ($driftItem['subject_key'] ?? '');

            if (trim($subjectKey) === '') {
                continue;
            }

            $recurrenceKey = $this->recurrenceKey(
                tenantId: $tenantId,
                baselineProfileId: $baselineProfileId,
                policyType: (string) ($driftItem['policy_type'] ?? ''),
                subjectKey: $subjectKey,
                changeType: (string) ($driftItem['change_type'] ?? ''),
            );

            $fingerprint = $recurrenceKey;

            $seenFingerprints[] = $fingerprint;

            $finding = Finding::query()
                ->where('managed_environment_id', $tenantId)
                ->where('fingerprint', $fingerprint)
                ->first();

            $isNewFinding = ! $finding instanceof Finding;

            if ($isNewFinding) {
                $finding = new Finding;
            } else {
                $this->observeFinding(
                    finding: $finding,
                    tenant: $tenant,
                    observedAt: $observedAt,
                    currentOperationRunId: (int) $this->operationRun->getKey(),
                    severity: (string) $driftItem['severity'],
                    slaPolicy: $slaPolicy,
                );
            }

            $finding->forceFill([
                'managed_environment_id' => $tenantId,
                'finding_type' => Finding::FINDING_TYPE_DRIFT,
                'source' => 'baseline.compare',
                'scope_key' => $scopeKey,
                'subject_type' => $driftItem['subject_type'],
                'subject_external_id' => $driftItem['subject_external_id'],
                'severity' => $driftItem['severity'],
                'fingerprint' => $fingerprint,
                'recurrence_key' => $recurrenceKey,
                'evidence_jsonb' => $driftItem['evidence'],
                'evidence_fidelity' => $driftItem['evidence_fidelity'] ?? EvidenceProvenance::FidelityMeta,
                'baseline_operation_run_id' => null,
                'current_operation_run_id' => (int) $this->operationRun->getKey(),
            ]);

            if ($isNewFinding) {
                $severity = (string) $driftItem['severity'];
                $slaDays = $slaPolicy->daysForSeverity($severity, $tenant);

                $finding->forceFill([
                    'status' => Finding::STATUS_NEW,
                    'reopened_at' => null,
                    'resolved_at' => null,
                    'resolved_reason' => null,
                    'acknowledged_at' => null,
                    'acknowledged_by_user_id' => null,
                    'first_seen_at' => $observedAt,
                    'last_seen_at' => $observedAt,
                    'times_seen' => 1,
                    'sla_days' => $slaDays,
                    'due_at' => $slaPolicy->dueAtForSeverity($severity, $tenant, $observedAt),
                ]);

                $createdCount++;
            } elseif ((string) $finding->status === Finding::STATUS_RESOLVED) {
                $resolvedAt = $finding->resolved_at !== null
                    ? CarbonImmutable::instance($finding->resolved_at)
                    : null;

                if ($resolvedAt === null || $observedAt->greaterThan($resolvedAt)) {
                    $finding->save();

                    app(FindingWorkflowService::class)->reopenBySystem(
                        finding: $finding,
                        tenant: $tenant,
                        reopenedAt: $observedAt,
                        operationRunId: (int) $this->operationRun->getKey(),
                    );

                    $reopenedCount++;
                } else {
                    $unchangedCount++;
                }
            } else {
                $unchangedCount++;
            }

            $finding->save();
            $processedCount++;
        }

        return [
            'processed_count' => $processedCount,
            'created_count' => $createdCount,
            'reopened_count' => $reopenedCount,
            'unchanged_count' => $unchangedCount,
            'seen_fingerprints' => array_values(array_unique($seenFingerprints)),
        ];
    }

    private function observeFinding(
        Finding $finding,
        ManagedEnvironment $tenant,
        CarbonImmutable $observedAt,
        int $currentOperationRunId,
        string $severity,
        FindingSlaPolicy $slaPolicy,
    ): void
    {
        if ($finding->first_seen_at === null) {
            $finding->first_seen_at = $observedAt;
        }

        $firstSeenAt = CarbonImmutable::instance($finding->first_seen_at);

        if ($finding->last_seen_at === null || $observedAt->greaterThan(CarbonImmutable::instance($finding->last_seen_at))) {
            $finding->last_seen_at = $observedAt;
        }

        $timesSeen = is_numeric($finding->times_seen) ? (int) $finding->times_seen : 0;

        if ((int) ($finding->current_operation_run_id ?? 0) !== $currentOperationRunId) {
            $finding->times_seen = max(0, $timesSeen) + 1;
        } elseif ($timesSeen < 1) {
            $finding->times_seen = 1;
        }

        if ($finding->sla_days === null) {
            $finding->sla_days = $slaPolicy->daysForSeverity($severity, $tenant);
        }

        if ($finding->due_at === null) {
            $finding->due_at = $slaPolicy->dueAtForSeverity($severity, $tenant, $firstSeenAt);
        }
    }

    /**
     * Stable identity for baseline-compare findings, scoped to a baseline profile + subject key.
     */
    private function recurrenceKey(
        int $tenantId,
        int $baselineProfileId,
        string $policyType,
        string $subjectKey,
        string $changeType,
    ): string {
        $parts = [
            (string) $tenantId,
            (string) $baselineProfileId,
            $this->normalizeKeyPart($policyType),
            $this->normalizeKeyPart($subjectKey),
            $this->normalizeKeyPart($changeType),
        ];

        return hash('sha256', implode('|', $parts));
    }

    private function normalizeKeyPart(string $value): string
    {
        return trim(mb_strtolower($value));
    }

    /**
     * @param  array<int, array{change_type: string}>  $driftResults
     * @return array<string, int>
     */
    private function countByChangeType(array $driftResults): array
    {
        $counts = [];

        foreach ($driftResults as $item) {
            $changeType = (string) ($item['change_type'] ?? '');

            if ($changeType === '') {
                continue;
            }

            $counts[$changeType] = ($counts[$changeType] ?? 0) + 1;
        }

        ksort($counts);

        return $counts;
    }

    /**
     * @param  array<int, array{severity: string}>  $driftResults
     * @return array<string, int>
     */
    private function countBySeverity(array $driftResults): array
    {
        $counts = [];

        foreach ($driftResults as $item) {
            $severity = $item['severity'];
            $counts[$severity] = ($counts[$severity] ?? 0) + 1;
        }

        return $counts;
    }

    /**
     * @return array<string, string>
     */
    private function resolveSeverityMapping(Workspace $workspace, SettingsResolver $settingsResolver): array
    {
        try {
            $mapping = $settingsResolver->resolveValue(
                workspace: $workspace,
                domain: 'baseline',
                key: 'severity_mapping',
            );
        } catch (\InvalidArgumentException) {
            // Settings keys are registry-backed; if this key is missing (e.g. during rollout),
            // fall back to built-in defaults rather than failing the entire compare run.
            return [];
        }

        return is_array($mapping) ? $mapping : [];
    }

    /**
     * @param  array<string, string>  $severityMapping
     */
    private function severityForChangeType(array $severityMapping, string $changeType): string
    {
        $severity = $severityMapping[$changeType] ?? null;

        if (! is_string($severity) || $severity === '') {
            return match ($changeType) {
                'missing_policy' => Finding::SEVERITY_HIGH,
                'different_version' => Finding::SEVERITY_MEDIUM,
                default => Finding::SEVERITY_LOW,
            };
        }

        return $severity;
    }

    private function auditStarted(
        AuditLogger $auditLogger,
        ManagedEnvironment $tenant,
        BaselineProfile $profile,
        ?User $initiator,
        BaselineCaptureMode $captureMode,
        int $subjectsTotal,
        BaselineScope $effectiveScope,
    ): void {
        $auditLogger->log(
            tenant: $tenant,
            action: 'baseline.compare.started',
            context: [
                'metadata' => [
                    'operation_run_id' => (int) $this->operationRun->getKey(),
                    'baseline_profile_id' => (int) $profile->getKey(),
                    'baseline_profile_name' => (string) $profile->name,
                    'purpose' => PolicyVersionCapturePurpose::BaselineCompare->value,
                    'capture_mode' => $captureMode->value,
                    'scope_types_total' => count($effectiveScope->allTypes()),
                    'subjects_total' => $subjectsTotal,
                ],
            ],
            actorId: $initiator?->id,
            actorEmail: $initiator?->email,
            actorName: $initiator?->name,
            resourceType: 'baseline_profile',
            resourceId: (string) $profile->getKey(),
        );
    }

    private function auditCompleted(
        AuditLogger $auditLogger,
        ManagedEnvironment $tenant,
        BaselineProfile $profile,
        ?User $initiator,
        BaselineCaptureMode $captureMode,
        int $subjectsTotal,
        array $evidenceCaptureStats,
        array $gaps,
        array $summaryCounts,
    ): void {
        $auditLogger->log(
            tenant: $tenant,
            action: 'baseline.compare.completed',
            context: [
                'metadata' => [
                    'operation_run_id' => (int) $this->operationRun->getKey(),
                    'baseline_profile_id' => (int) $profile->getKey(),
                    'baseline_profile_name' => (string) $profile->name,
                    'purpose' => PolicyVersionCapturePurpose::BaselineCompare->value,
                    'capture_mode' => $captureMode->value,
                    'subjects_total' => $subjectsTotal,
                    'findings_total' => $summaryCounts['total'] ?? 0,
                    'high' => $summaryCounts['high'] ?? 0,
                    'medium' => $summaryCounts['medium'] ?? 0,
                    'low' => $summaryCounts['low'] ?? 0,
                    'evidence_capture' => $evidenceCaptureStats,
                    'gaps' => $gaps,
                ],
            ],
            actorId: $initiator?->id,
            actorEmail: $initiator?->email,
            actorName: $initiator?->name,
            resourceType: 'operation_run',
            resourceId: (string) $this->operationRun->getKey(),
        );
    }
}
