<?php

declare(strict_types=1);

use App\Jobs\CompareBaselineToTenantJob;
use App\Models\AuditLog;
use App\Models\BaselineProfile;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Services\Findings\FindingWorkflowService;
use App\Support\Audit\AuditActionId;
use Carbon\CarbonImmutable;

afterEach(function (): void {
    CarbonImmutable::setTestNow();
});

if (! function_exists('concurrencyBaselineCompareDriftItem')) {
    /**
     * @return array<string, mixed>
     */
    function concurrencyBaselineCompareDriftItem(int $baselineProfileId, int $compareOperationRunId, string $subjectKey): array
    {
        return [
            'change_type' => 'different_version',
            'severity' => Finding::SEVERITY_MEDIUM,
            'subject_type' => 'policy',
            'subject_external_id' => $subjectKey,
            'subject_key' => $subjectKey,
            'policy_type' => 'deviceConfiguration',
            'baseline_hash' => 'baseline',
            'current_hash' => 'current',
            'evidence_fidelity' => 'meta',
            'evidence' => [
                'change_type' => 'different_version',
                'policy_type' => 'deviceConfiguration',
                'subject_key' => $subjectKey,
                'summary' => ['kind' => 'policy_snapshot'],
                'baseline' => ['policy_version_id' => null, 'hash' => 'baseline'],
                'current' => ['policy_version_id' => null, 'hash' => 'current'],
                'fidelity' => 'meta',
                'provenance' => [
                    'baseline_profile_id' => $baselineProfileId,
                    'baseline_snapshot_id' => 1,
                    'compare_operation_run_id' => $compareOperationRunId,
                    'inventory_sync_run_id' => null,
                ],
            ],
        ];
    }
}

if (! function_exists('invokeConcurrencyBaselineCompareUpsertFindings')) {
    /**
     * @param  array<int, array<string, mixed>>  $driftResults
     * @return array{processed_count:int,created_count:int,reopened_count:int,unchanged_count:int,seen_fingerprints:array<int,string>}
     */
    function invokeConcurrencyBaselineCompareUpsertFindings(
        CompareBaselineToTenantJob $job,
        \App\Models\ManagedEnvironment $tenant,
        BaselineProfile $profile,
        string $scopeKey,
        array $driftResults,
    ): array {
        $reflection = new ReflectionMethod($job, 'upsertFindings');

        /** @var array{processed_count:int,created_count:int,reopened_count:int,unchanged_count:int,seen_fingerprints:array<int,string>} $result */
        $result = $reflection->invoke($job, $tenant, $profile, $scopeKey, $driftResults);

        return $result;
    }
}

it('does not reopen when a later human resolve beats an older automated recurrence observation', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'manager');

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

    $scopeKey = 'baseline_profile:'.$profile->getKey();
    $run1 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T08:00:00Z'));
    invokeConcurrencyBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run1),
        $tenant,
        $profile,
        $scopeKey,
        [concurrencyBaselineCompareDriftItem((int) $profile->getKey(), (int) $run1->getKey(), 'policy-concurrency')],
    );

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

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T10:00:00Z'));
    app(FindingWorkflowService::class)->resolve($finding, $tenant, $user, Finding::RESOLVE_REASON_REMEDIATED);

    $run2 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T09:00:00Z'));
    $result = invokeConcurrencyBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run2),
        $tenant,
        $profile,
        $scopeKey,
        [concurrencyBaselineCompareDriftItem((int) $profile->getKey(), (int) $run2->getKey(), 'policy-concurrency')],
    );

    expect($result['reopened_count'])->toBe(0)
        ->and($finding->fresh()->status)->toBe(Finding::STATUS_RESOLVED)
        ->and(AuditLog::query()
            ->where('resource_type', 'finding')
            ->where('resource_id', (string) $finding->getKey())
            ->where('action', AuditActionId::FindingReopened->value)
            ->count())->toBe(0);
});

it('keeps closed findings terminal when recurrence is detected after a human close', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'manager');

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

    $scopeKey = 'baseline_profile:'.$profile->getKey();
    $run1 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T08:00:00Z'));
    invokeConcurrencyBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run1),
        $tenant,
        $profile,
        $scopeKey,
        [concurrencyBaselineCompareDriftItem((int) $profile->getKey(), (int) $run1->getKey(), 'policy-closed')],
    );

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

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T09:00:00Z'));
    app(FindingWorkflowService::class)->close($finding, $tenant, $user, Finding::CLOSE_REASON_DUPLICATE);

    $run2 = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
    ]);

    CarbonImmutable::setTestNow(CarbonImmutable::parse('2026-03-18T10:00:00Z'));
    $result = invokeConcurrencyBaselineCompareUpsertFindings(
        new CompareBaselineToTenantJob($run2),
        $tenant,
        $profile,
        $scopeKey,
        [concurrencyBaselineCompareDriftItem((int) $profile->getKey(), (int) $run2->getKey(), 'policy-closed')],
    );

    expect($result['reopened_count'])->toBe(0)
        ->and($finding->fresh()->status)->toBe(Finding::STATUS_CLOSED)
        ->and(AuditLog::query()
            ->where('resource_type', 'finding')
            ->where('resource_id', (string) $finding->getKey())
            ->where('action', AuditActionId::FindingReopened->value)
            ->count())->toBe(0);
});
