<?php

use App\Jobs\CaptureBaselineSnapshotJob;
use App\Jobs\CompareBaselineToTenantJob;
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\WorkspaceSetting;
use App\Services\Baselines\BaselineSnapshotIdentity;
use App\Services\Baselines\InventoryMetaContract;
use App\Services\Drift\DriftHasher;
use App\Services\Intune\AuditLogger;
use App\Services\OperationRunService;
use App\Services\Settings\SettingsResolver;
use App\Support\Baselines\BaselineSubjectKey;
use App\Support\OperationRunType;
use App\Support\OpsUx\OperationSummaryKeys;

use function Pest\Laravel\mock;

// --- T041: Compare idempotent finding fingerprint tests ---

it('creates drift findings when baseline and tenant inventory differ', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $policyType = 'deviceConfiguration';

    $displayNameA = 'Policy A';
    $subjectKeyA = BaselineSubjectKey::fromDisplayName($displayNameA);
    expect($subjectKeyA)->not->toBeNull();
    $workspaceSafeExternalIdA = BaselineSubjectKey::workspaceSafeSubjectExternalId($policyType, (string) $subjectKeyA);
    $baselineHashA = app(BaselineSnapshotIdentity::class)->hashItemContent(
        policyType: $policyType,
        subjectExternalId: 'policy-a-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE_A'],
    );

    $displayNameB = 'Policy B';
    $subjectKeyB = BaselineSubjectKey::fromDisplayName($displayNameB);
    expect($subjectKeyB)->not->toBeNull();
    $workspaceSafeExternalIdB = BaselineSubjectKey::workspaceSafeSubjectExternalId($policyType, (string) $subjectKeyB);
    $baselineHashB = app(BaselineSnapshotIdentity::class)->hashItemContent(
        policyType: $policyType,
        subjectExternalId: 'policy-b-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE_B'],
    );

    // Baseline has policyA and policyB
    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalIdA,
        'subject_key' => (string) $subjectKeyA,
        'policy_type' => $policyType,
        'baseline_hash' => $baselineHashA,
        'meta_jsonb' => ['display_name' => $displayNameA],
    ]);
    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalIdB,
        'subject_key' => (string) $subjectKeyB,
        'policy_type' => $policyType,
        'baseline_hash' => $baselineHashB,
        'meta_jsonb' => ['display_name' => $displayNameB],
    ]);

    // ManagedEnvironment has policyA (different content) and policyC (unexpected)
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-a-uuid',
        'policy_type' => $policyType,
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_A'],
        'display_name' => $displayNameA,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-c-uuid',
        'policy_type' => $policyType,
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_C'],
        'display_name' => 'Policy C',
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    $job = new CompareBaselineToTenantJob($run);
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run->refresh();
    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');
    expect(data_get($run->context, 'baseline_compare.strategy.key'))->toBe('intune_policy')
        ->and(data_get($run->context, 'baseline_compare.strategy.selection_state'))->toBe('supported')
        ->and(data_get($run->context, 'baseline_compare.strategy.state_counts.drift'))->toBe(3);

    $context = is_array($run->context) ? $run->context : [];
    $countsByChangeType = $context['findings']['counts_by_change_type'] ?? null;
    expect($countsByChangeType)->toBe([
        'different_version' => 1,
        'missing_policy' => 1,
        'unexpected_policy' => 1,
    ]);

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $findings = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->get();

    // policyB missing (high), policyA different (medium), policyC unexpected (low) = 3 findings
    expect($findings->count())->toBe(3);
    expect($findings->every(fn (Finding $finding): bool => filled($finding->recurrence_key) && $finding->recurrence_key === $finding->fingerprint))->toBeTrue();

    // Lifecycle v2 fields must be initialized for new findings.
    expect($findings->pluck('first_seen_at')->filter()->count())->toBe($findings->count());
    expect($findings->pluck('last_seen_at')->filter()->count())->toBe($findings->count());
    expect($findings->pluck('times_seen')->every(fn ($value) => (int) $value === 1))->toBeTrue();

    $severities = $findings->pluck('severity')->sort()->values()->all();
    expect($severities)->toContain(Finding::SEVERITY_HIGH);
    expect($severities)->toContain(Finding::SEVERITY_MEDIUM);
    expect($severities)->toContain(Finding::SEVERITY_LOW);
});

it('does not fail compare when baseline severity mapping setting is missing', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $policyType = 'deviceConfiguration';

    $displayNameA = 'Policy A';
    $subjectKeyA = BaselineSubjectKey::fromDisplayName($displayNameA);
    expect($subjectKeyA)->not->toBeNull();
    $workspaceSafeExternalIdA = BaselineSubjectKey::workspaceSafeSubjectExternalId($policyType, (string) $subjectKeyA);
    $baselineHashA = app(BaselineSnapshotIdentity::class)->hashItemContent(
        policyType: $policyType,
        subjectExternalId: 'policy-a-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE_A'],
    );

    $displayNameB = 'Policy B';
    $subjectKeyB = BaselineSubjectKey::fromDisplayName($displayNameB);
    expect($subjectKeyB)->not->toBeNull();
    $workspaceSafeExternalIdB = BaselineSubjectKey::workspaceSafeSubjectExternalId($policyType, (string) $subjectKeyB);
    $baselineHashB = app(BaselineSnapshotIdentity::class)->hashItemContent(
        policyType: $policyType,
        subjectExternalId: 'policy-b-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE_B'],
    );

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalIdA,
        'subject_key' => (string) $subjectKeyA,
        'policy_type' => $policyType,
        'baseline_hash' => $baselineHashA,
        'meta_jsonb' => ['display_name' => $displayNameA],
    ]);
    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalIdB,
        'subject_key' => (string) $subjectKeyB,
        'policy_type' => $policyType,
        'baseline_hash' => $baselineHashB,
        'meta_jsonb' => ['display_name' => $displayNameB],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-a-uuid',
        'policy_type' => $policyType,
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_A'],
        'display_name' => $displayNameA,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    $realSettingsResolver = app(SettingsResolver::class);

    $settingsResolver = mock(SettingsResolver::class);
    $settingsResolver
        ->shouldReceive('resolveValue')
        ->andReturnUsing(function ($workspace, $domain, $key, $tenant = null) use ($realSettingsResolver): mixed {
            if ($domain === 'baseline' && $key === 'severity_mapping') {
                throw new InvalidArgumentException('Unknown setting key: baseline.severity_mapping');
            }

            return $realSettingsResolver->resolveValue($workspace, $domain, $key, $tenant);
        });

    $baselineAutoCloseService = new \App\Services\Baselines\BaselineAutoCloseService($settingsResolver);

    (new CompareBaselineToTenantJob($run))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
        settingsResolver: $settingsResolver,
        baselineAutoCloseService: $baselineAutoCloseService,
    );

    $run->refresh();
    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $findings = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->get();

    // policyB missing (high), policyA different (medium) = 2 findings.
    expect($findings->count())->toBe(2);
    expect($findings->pluck('severity')->all())->toContain(Finding::SEVERITY_HIGH);
    expect($findings->pluck('severity')->all())->toContain(Finding::SEVERITY_MEDIUM);
});

it('treats inventory items not seen in latest inventory sync as missing', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['settingsCatalogPolicy']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $olderInventoryRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['settingsCatalogPolicy' => 'succeeded'],
        attributes: [
            'selection_hash' => 'older',
            'selection_payload' => ['policy_types' => ['settingsCatalogPolicy']],
            'finished_at' => now()->subMinutes(5),
        ],
    );

    createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['settingsCatalogPolicy' => 'succeeded'],
        attributes: [
            'selection_hash' => 'latest',
            'selection_payload' => ['policy_types' => ['settingsCatalogPolicy']],
            'finished_at' => now(),
        ],
    );

    $displayName = 'Settings Catalog A';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('settingsCatalogPolicy', (string) $subjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'settingsCatalogPolicy',
        'baseline_hash' => hash('sha256', 'content-a'),
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    // Inventory item exists, but it was NOT observed in the latest sync run.
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'settings-catalog-policy-uuid',
        'policy_type' => 'settingsCatalogPolicy',
        'display_name' => $displayName,
        'meta_jsonb' => ['etag' => 'abc'],
        'last_seen_operation_run_id' => (int) $olderInventoryRun->getKey(),
        'last_seen_at' => now()->subMinutes(5),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['settingsCatalogPolicy']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run->refresh();
    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $findings = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->get();

    expect($findings->count())->toBe(1);
    expect($findings->first()?->evidence_jsonb['change_type'] ?? null)->toBe('missing_policy');
});

it('uses recurrence-key identity (no hashes in fingerprint) and increments times_seen at most once per run', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $builder = app(InventoryMetaContract::class);
    $hasher = app(DriftHasher::class);

    $baselineContract = $builder->build(
        policyType: 'deviceConfiguration',
        subjectExternalId: 'policy-x-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE'],
    );

    $displayName = 'Policy X';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $subjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $hasher->hashNormalized($baselineContract),
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-x-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_1'],
        'display_name' => $displayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);

    $run1 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    $job = new CompareBaselineToTenantJob($run1);
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $finding = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->sole();

    expect($finding->recurrence_key)->not->toBeNull();
    expect($finding->fingerprint)->toBe($finding->recurrence_key);
    expect($finding->times_seen)->toBe(1);

    $fingerprint = (string) $finding->fingerprint;
    $currentHash1 = (string) data_get($finding->evidence_jsonb, 'current.hash');
    expect($currentHash1)->not->toBe('');
    expect(data_get($finding->evidence_jsonb, 'summary.kind'))->toBe('policy_snapshot');

    // Retry the same run ID (job retry): times_seen MUST NOT increment twice for the same run.
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $finding->refresh();
    expect($finding->times_seen)->toBe(1);
    expect((string) $finding->fingerprint)->toBe($fingerprint);
    expect(data_get($finding->evidence_jsonb, 'summary.kind'))->toBe('policy_snapshot');
    expect((string) data_get($finding->evidence_jsonb, 'current.hash'))->toBe($currentHash1);

    // Change inventory evidence (hash changes) and run compare again with a new OperationRun.
    InventoryItem::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('policy_type', 'deviceConfiguration')
        ->where('external_id', 'policy-x-uuid')
        ->update([
            'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_2'],
        ]);

    $run2 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run2))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $finding->refresh();
    expect((string) $finding->fingerprint)->toBe($fingerprint);
    expect($finding->times_seen)->toBe(2);
    expect(data_get($finding->evidence_jsonb, 'summary.kind'))->toBe('policy_snapshot');
    expect((string) data_get($finding->evidence_jsonb, 'current.hash'))->not->toBe($currentHash1);
});

it('repairs missing due-state fields on an existing open drift finding without extending the original due date', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    \Carbon\CarbonImmutable::setTestNow(\Carbon\CarbonImmutable::parse('2026-02-24T10:00:00Z'));

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $builder = app(InventoryMetaContract::class);
    $hasher = app(DriftHasher::class);

    $baselineContract = $builder->build(
        policyType: 'deviceConfiguration',
        subjectExternalId: 'policy-x-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE'],
    );

    $displayName = 'Policy X';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $subjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $hasher->hashNormalized($baselineContract),
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-x-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT_1'],
        'display_name' => $displayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);

    $run1 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run1))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $finding = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->sole();

    $expectedSlaDays = (int) $finding->sla_days;
    $expectedDueAt = $finding->due_at?->toIso8601String();

    expect($expectedSlaDays)->toBeGreaterThan(0)
        ->and($expectedDueAt)->not->toBeNull();

    $finding->forceFill([
        'sla_days' => null,
        'due_at' => null,
    ])->save();

    \Carbon\CarbonImmutable::setTestNow(\Carbon\CarbonImmutable::parse('2026-02-24T11:00:00Z'));

    $run2 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run2))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $finding->refresh();

    expect($finding->first_seen_at?->toIso8601String())->toBe('2026-02-24T10:00:00+00:00')
        ->and($finding->last_seen_at?->toIso8601String())->toBe('2026-02-24T11:00:00+00:00')
        ->and($finding->times_seen)->toBe(2)
        ->and($finding->sla_days)->toBe($expectedSlaDays)
        ->and($finding->due_at?->toIso8601String())->toBe($expectedDueAt);

    \Carbon\CarbonImmutable::setTestNow();
});

it('does not create new finding identities when a new snapshot is captured', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $builder = app(InventoryMetaContract::class);
    $hasher = app(DriftHasher::class);

    $baselineContract = $builder->build(
        policyType: 'deviceConfiguration',
        subjectExternalId: 'policy-x-uuid',
        metaJsonb: ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_BASELINE'],
    );
    $baselineHash = $hasher->hashNormalized($baselineContract);

    $displayName = 'Policy X';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $subjectKey);

    $snapshot1 = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot1->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $baselineHash,
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'policy-x-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E_CURRENT'],
        'display_name' => $displayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run1 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot1->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run1))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    $finding = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->sole();

    expect($finding->times_seen)->toBe(1);
    $fingerprint1 = (string) $finding->fingerprint;

    $snapshot2 = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot2->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $baselineHash,
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    $run2 = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot2->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run2))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $findings = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->where('scope_key', $scopeKey)
        ->orderBy('id')
        ->get();

    expect($findings)->toHaveCount(1);
    expect((string) $findings->first()?->fingerprint)->toBe($fingerprint1);
    expect((int) $findings->first()?->times_seen)->toBe(2);
});

it('creates zero findings when baseline matches tenant inventory exactly', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    // Baseline item
    $metaContent = [
        'odata_type' => '#microsoft.graph.deviceConfiguration',
        'etag' => 'E1',
        'scope_tag_ids' => ['scope-2', 'scope-1'],
        'assignment_target_count' => 3,
    ];

    $builder = app(InventoryMetaContract::class);
    $driftHasher = app(DriftHasher::class);
    $contentHash = $driftHasher->hashNormalized($builder->build(
        policyType: 'deviceConfiguration',
        subjectExternalId: 'matching-uuid',
        metaJsonb: $metaContent,
    ));

    $displayName = 'Matching Policy';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $subjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $contentHash,
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    // ManagedEnvironment inventory with same content → same hash
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'matching-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => $metaContent,
        'display_name' => $displayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    $job = new CompareBaselineToTenantJob($run);
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run->refresh();
    expect($run->status)->toBe('completed');
    expect($run->outcome)->toBe('succeeded');

    $counts = is_array($run->summary_counts) ? $run->summary_counts : [];
    expect((int) ($counts['total'] ?? -1))->toBe(0);

    $findings = Finding::query()
        ->where('managed_environment_id', $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->count();

    expect($findings)->toBe(0);
});

it('does not create missing_policy findings for baseline snapshot items outside effective scope', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $metaContent = [
        'odata_type' => '#microsoft.graph.deviceConfiguration',
        'etag' => 'E1',
        'scope_tag_ids' => ['scope-2', 'scope-1'],
        'assignment_target_count' => 3,
    ];

    $builder = app(InventoryMetaContract::class);
    $driftHasher = app(DriftHasher::class);
    $contentHash = $driftHasher->hashNormalized($builder->build(
        policyType: 'deviceConfiguration',
        subjectExternalId: 'matching-uuid',
        metaJsonb: $metaContent,
    ));

    $displayName = 'Matching Policy';
    $subjectKey = BaselineSubjectKey::fromDisplayName($displayName);
    expect($subjectKey)->not->toBeNull();
    $workspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $subjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $workspaceSafeExternalId,
        'subject_key' => (string) $subjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => $contentHash,
        'meta_jsonb' => ['display_name' => $displayName],
    ]);

    $foundationDisplayName = 'Foundation Template';
    $foundationSubjectKey = BaselineSubjectKey::fromDisplayName($foundationDisplayName);
    expect($foundationSubjectKey)->not->toBeNull();
    $foundationWorkspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId(
        'notificationMessageTemplate',
        (string) $foundationSubjectKey,
    );

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $foundationWorkspaceSafeExternalId,
        'subject_key' => (string) $foundationSubjectKey,
        'policy_type' => 'notificationMessageTemplate',
        'baseline_hash' => hash('sha256', 'foundation-content'),
        'meta_jsonb' => ['display_name' => $foundationDisplayName],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'matching-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => $metaContent,
        'display_name' => $displayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'foundation-uuid',
        'policy_type' => 'notificationMessageTemplate',
        'meta_jsonb' => ['some' => 'value'],
        'display_name' => $foundationDisplayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run = $run->fresh();
    $counts = is_array($run->summary_counts) ? $run->summary_counts : [];
    expect((int) ($counts['total'] ?? -1))->toBe(0);

    $scopeKey = 'baseline_profile:'.$profile->getKey();

    expect(
        Finding::query()
            ->where('managed_environment_id', $tenant->getKey())
            ->where('source', 'baseline.compare')
            ->where('scope_key', $scopeKey)
            ->count()
    )->toBe(0);
});

// --- T042: Summary counts severity breakdown tests ---

it('writes severity breakdown in summary_counts', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    // 2 baseline items: one will be missing (high), one will be different (medium)
    $missingDisplayName = 'Missing Policy';
    $missingSubjectKey = BaselineSubjectKey::fromDisplayName($missingDisplayName);
    expect($missingSubjectKey)->not->toBeNull();
    $missingWorkspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $missingSubjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $missingWorkspaceSafeExternalId,
        'subject_key' => (string) $missingSubjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'missing-content'),
        'meta_jsonb' => ['display_name' => $missingDisplayName],
    ]);

    $changedDisplayName = 'Changed Policy';
    $changedSubjectKey = BaselineSubjectKey::fromDisplayName($changedDisplayName);
    expect($changedSubjectKey)->not->toBeNull();
    $changedWorkspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $changedSubjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $changedWorkspaceSafeExternalId,
        'subject_key' => (string) $changedSubjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'original-content'),
        'meta_jsonb' => ['display_name' => $changedDisplayName],
    ]);

    // ManagedEnvironment only has changed-uuid with different content + extra-uuid (unexpected)
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'changed-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['modified_content' => true],
        'display_name' => $changedDisplayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'extra-uuid',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['extra_content' => true],
        'display_name' => 'Extra Policy',
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    $job = new CompareBaselineToTenantJob($run);
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run->refresh();
    $counts = is_array($run->summary_counts) ? $run->summary_counts : [];

    expect((int) ($counts['total'] ?? -1))->toBe(3);
    expect((int) ($counts['high'] ?? -1))->toBe(1);   // missing-uuid
    expect((int) ($counts['medium'] ?? -1))->toBe(1);  // changed-uuid
    expect((int) ($counts['low'] ?? -1))->toBe(1);     // extra-uuid
});

it('writes result context with findings breakdown', function () {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    // One missing policy
    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => 'gone-uuid',
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'gone-content'),
    ]);

    $opService = app(OperationRunService::class);
    $run = $opService->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    $job = new CompareBaselineToTenantJob($run);
    $job->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $opService,
    );

    $run->refresh();
    $context = is_array($run->context) ? $run->context : [];
    $result = $context['result'] ?? [];

    expect($result)->toHaveKey('findings_total');
    expect($result)->toHaveKey('findings_upserted');
    expect($result)->toHaveKey('severity_breakdown');
    expect((int) $result['findings_total'])->toBe(1);
    expect((int) $result['findings_upserted'])->toBe(1);
});

it('reopens a previously resolved baseline finding when the same drift reappears', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => 'policy-reappears',
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'baseline-content'),
        'meta_jsonb' => ['display_name' => 'Policy Reappears'],
    ]);

    $operationRuns = app(OperationRunService::class);

    $firstRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($firstRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $finding = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->sole();

    $finding->forceFill([
        'status' => Finding::STATUS_RESOLVED,
        'resolved_at' => now()->subMinute(),
        'resolved_reason' => Finding::RESOLVE_REASON_REMEDIATED,
    ])->save();

    $firstRun->update(['completed_at' => now()->subMinute()]);

    $secondRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($secondRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $finding->refresh();
    expect($finding->status)->toBe(Finding::STATUS_REOPENED);
    expect($finding->resolved_at)->toBeNull();
    expect($finding->resolved_reason)->toBeNull();
    expect($finding->reopened_at)->not->toBeNull();
});

it('preserves an existing open workflow status when the same baseline drift is seen again', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => 'triaged-policy',
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'baseline-content'),
        'meta_jsonb' => ['display_name' => 'Triaged Policy'],
    ]);

    $operationRuns = app(OperationRunService::class);

    $firstRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($firstRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $finding = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->sole();

    $finding->forceFill([
        'status' => Finding::STATUS_TRIAGED,
        'triaged_at' => now()->subMinute(),
    ])->save();

    $firstRun->update(['completed_at' => now()->subMinute()]);

    $secondRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($secondRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $finding->refresh();
    expect($finding->status)->toBe(Finding::STATUS_TRIAGED);
    expect($finding->current_operation_run_id)->toBe((int) $secondRun->getKey());
});

it('applies the workspace baseline severity mapping by change type', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    WorkspaceSetting::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'domain' => 'baseline',
        'key' => 'severity_mapping',
        'value' => [
            'missing_policy' => Finding::SEVERITY_CRITICAL,
            'different_version' => Finding::SEVERITY_LOW,
            'unexpected_policy' => Finding::SEVERITY_MEDIUM,
        ],
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration']],
    ]);

    $snapshot = BaselineSnapshot::factory()->create([
        'workspace_id' => $tenant->workspace_id,
        'baseline_profile_id' => $profile->getKey(),
    ]);

    $profile->update(['active_snapshot_id' => $snapshot->getKey()]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    $missingDisplayName = 'Missing Policy';
    $missingSubjectKey = BaselineSubjectKey::fromDisplayName($missingDisplayName);
    expect($missingSubjectKey)->not->toBeNull();
    $missingWorkspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $missingSubjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $missingWorkspaceSafeExternalId,
        'subject_key' => (string) $missingSubjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'baseline-a'),
        'meta_jsonb' => ['display_name' => $missingDisplayName],
    ]);

    $differentDisplayName = 'Different Policy';
    $differentSubjectKey = BaselineSubjectKey::fromDisplayName($differentDisplayName);
    expect($differentSubjectKey)->not->toBeNull();
    $differentWorkspaceSafeExternalId = BaselineSubjectKey::workspaceSafeSubjectExternalId('deviceConfiguration', (string) $differentSubjectKey);

    BaselineSnapshotItem::factory()->create([
        'baseline_snapshot_id' => $snapshot->getKey(),
        'subject_type' => 'policy',
        'subject_external_id' => $differentWorkspaceSafeExternalId,
        'subject_key' => (string) $differentSubjectKey,
        'policy_type' => 'deviceConfiguration',
        'baseline_hash' => hash('sha256', 'baseline-b'),
        'meta_jsonb' => ['display_name' => $differentDisplayName],
    ]);

    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'different-policy',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['different_content' => true],
        'display_name' => $differentDisplayName,
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);
    InventoryItem::factory()->create([
        'managed_environment_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
        'external_id' => 'unexpected-policy',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['unexpected' => true],
        'display_name' => 'Unexpected Policy',
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $operationRuns = app(OperationRunService::class);
    $run = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => (int) $snapshot->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration']],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($run))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $findings = Finding::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('source', 'baseline.compare')
        ->get()
        ->keyBy(fn (Finding $finding): string => (string) data_get($finding->evidence_jsonb, 'change_type'));

    expect($findings['missing_policy']->severity)->toBe(Finding::SEVERITY_CRITICAL);
    expect($findings['different_version']->severity)->toBe(Finding::SEVERITY_LOW);
    expect($findings['unexpected_policy']->severity)->toBe(Finding::SEVERITY_MEDIUM);
});

it('writes numeric-only summary_counts with whitelisted keys for capture and compare runs', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'scope_jsonb' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
    ]);

    $inventorySyncRun = createInventorySyncOperationRunWithCoverage(
        tenant: $tenant,
        statusByType: ['deviceConfiguration' => 'succeeded'],
    );

    InventoryItem::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'external_id' => 'policy-a',
        'policy_type' => 'deviceConfiguration',
        'meta_jsonb' => ['odata_type' => '#microsoft.graph.deviceConfiguration', 'etag' => 'E1'],
        'last_seen_operation_run_id' => (int) $inventorySyncRun->getKey(),
        'last_seen_at' => now(),
    ]);

    $operationRuns = app(OperationRunService::class);

    $captureRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCapture->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'source_environment_id' => (int) $tenant->getKey(),
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CaptureBaselineSnapshotJob($captureRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(InventoryMetaContract::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $captureRun->refresh();

    $snapshotId = (int) ($profile->fresh()?->active_snapshot_id ?? 0);
    expect($snapshotId)->toBeGreaterThan(0);

    $compareRun = $operationRuns->ensureRunWithIdentity(
        tenant: $tenant,
        type: OperationRunType::BaselineCompare->value,
        identityInputs: ['baseline_profile_id' => (int) $profile->getKey()],
        context: [
            'baseline_profile_id' => (int) $profile->getKey(),
            'baseline_snapshot_id' => $snapshotId,
            'effective_scope' => ['policy_types' => ['deviceConfiguration'], 'foundation_types' => []],
        ],
        initiator: $user,
    );

    (new CompareBaselineToTenantJob($compareRun))->handle(
        app(BaselineSnapshotIdentity::class),
        app(AuditLogger::class),
        $operationRuns,
    );

    $allowedKeys = OperationSummaryKeys::all();

    foreach ([$captureRun->fresh(), $compareRun->fresh()] as $run) {
        $counts = is_array($run?->summary_counts) ? $run->summary_counts : [];

        foreach ($counts as $key => $value) {
            expect($allowedKeys)->toContain((string) $key);
            expect(is_array($value))->toBeFalse();
            expect(is_numeric($value))->toBeTrue();
        }
    }
});
