<?php

declare(strict_types=1);

use App\Exceptions\ReviewPackEvidenceResolutionException;
use App\Exceptions\Entitlements\WorkspaceEntitlementBlockedException;
use App\Filament\Widgets\ManagedEnvironment\ManagedEnvironmentReviewPackCard;
use App\Jobs\GenerateReviewPackJob;
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\PlatformUser;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\ManagedEnvironment;
use App\Notifications\OperationRunCompleted;
use App\Notifications\OperationRunQueued;
use App\Services\Entitlements\WorkspaceCommercialLifecycleResolver;
use App\Services\Evidence\EvidenceSnapshotService;
use App\Services\OperationRunService;
use App\Services\ReviewPackService;
use App\Services\Settings\SettingsWriter;
use App\Support\Auth\PlatformCapabilities;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\ReviewPackStatus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Queue;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    Storage::fake('exports');
});

it('treats only queued and generating review packs as active for card polling', function (string $status, ?string $expectedInterval): void {
    $tenant = ManagedEnvironment::factory()->create();

    $pack = match ($status) {
        ReviewPackStatus::Queued->value => ReviewPack::factory()->queued()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
        ]),
        ReviewPackStatus::Generating->value => ReviewPack::factory()->generating()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
        ]),
        ReviewPackStatus::Ready->value => ReviewPack::factory()->ready()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
        ]),
        ReviewPackStatus::Failed->value => ReviewPack::factory()->failed()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
        ]),
        ReviewPackStatus::Expired->value => ReviewPack::factory()->expired()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
        ]),
        default => ReviewPack::factory()->make([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
            'status' => $status,
        ]),
    };

    expect(ManagedEnvironmentReviewPackCard::resolvePollingInterval($pack))->toBe($expectedInterval);
})->with([
    'queued pack' => [ReviewPackStatus::Queued->value, '10s'],
    'generating pack' => [ReviewPackStatus::Generating->value, '10s'],
    'ready pack' => [ReviewPackStatus::Ready->value, null],
    'failed pack' => [ReviewPackStatus::Failed->value, null],
    'expired pack' => [ReviewPackStatus::Expired->value, null],
    'unknown status' => ['stalled', null],
]);

it('does not poll the review pack card when no pack exists', function (): void {
    expect(ManagedEnvironmentReviewPackCard::resolvePollingInterval(null))->toBeNull();
});

// ─── Helper ──────────────────────────────────────────────────

function seedTenantWithData(ManagedEnvironment $tenant): void
{
    StoredReport::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
        'payload' => [
            'posture_score' => 86,
            'required_count' => 14,
            'granted_count' => 12,
            'permissions' => [
                ['key' => 'DeviceManagementConfiguration.ReadWrite.All', 'status' => 'granted'],
            ],
        ],
    ]);

    StoredReport::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
        'payload' => [
            'roles' => [
                [
                    'displayName' => 'Global Administrator',
                    'userPrincipalName' => 'admin@contoso.com',
                    'role_template_id' => '62e90394-69f5-4237-9190-012177145e10',
                ],
            ],
        ],
    ]);

    Finding::factory()
        ->count(3)
        ->create(['managed_environment_id' => (int) $tenant->getKey()]);

    Finding::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'finding_type' => Finding::FINDING_TYPE_DRIFT,
    ]);

    OperationRun::factory()->forTenant($tenant)->create([
        'type' => OperationRunType::PolicySync->value,
    ]);
}

function createEvidenceSnapshotForReviewPack(ManagedEnvironment $tenant): EvidenceSnapshot
{
    /** @var EvidenceSnapshotService $service */
    $service = app(EvidenceSnapshotService::class);
    $payload = $service->buildSnapshotPayload($tenant);

    $snapshot = EvidenceSnapshot::query()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => EvidenceSnapshotStatus::Active->value,
        'fingerprint' => $payload['fingerprint'],
        'completeness_state' => $payload['completeness'],
        'summary' => $payload['summary'],
        'generated_at' => now(),
    ]);

    foreach ($payload['items'] as $item) {
        $snapshot->items()->create([
            'managed_environment_id' => (int) $tenant->getKey(),
            'workspace_id' => (int) $tenant->workspace_id,
            'dimension_key' => $item['dimension_key'],
            'state' => $item['state'],
            'required' => $item['required'],
            'source_kind' => $item['source_kind'],
            'source_record_type' => $item['source_record_type'],
            'source_record_id' => $item['source_record_id'],
            'source_fingerprint' => $item['source_fingerprint'],
            'measured_at' => $item['measured_at'],
            'freshness_at' => $item['freshness_at'],
            'summary_payload' => $item['summary_payload'],
            'sort_order' => $item['sort_order'],
        ]);
    }

    return $snapshot->load('items');
}

function suspendReviewPackGenerationWorkspaceForGenerationTest(ManagedEnvironment $tenant): void
{
    app(SettingsWriter::class)->updateWorkspaceCommercialLifecycle(
        actor: PlatformUser::factory()->create([
            'capabilities' => [
                PlatformCapabilities::ACCESS_SYSTEM_PANEL,
                PlatformCapabilities::DIRECTORY_VIEW,
                PlatformCapabilities::COMMERCIAL_LIFECYCLE_MANAGE,
            ],
            'is_active' => true,
        ]),
        workspace: $tenant->workspace,
        state: WorkspaceCommercialLifecycleResolver::STATE_SUSPENDED_READ_ONLY,
        reason: 'Generation notification boundary test',
    );
}

// ─── Happy Path ──────────────────────────────────────────────

it('generates a review pack end-to-end (happy path)', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    $snapshot = createEvidenceSnapshotForReviewPack($tenant);
    Notification::fake();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user, [
        'include_pii' => true,
        'include_operations' => true,
    ]);

    expect($pack)->toBeInstanceOf(ReviewPack::class);
    expect($pack->status)->toBe(ReviewPackStatus::Queued->value);

    // Dispatch the queued job synchronously
    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );
    app()->call([$job, 'handle']);

    $pack->refresh();

    expect($pack->status)->toBe(ReviewPackStatus::Ready->value);
    expect($pack->sha256)->toBeString()->not->toBeEmpty();
    expect($pack->file_size)->toBeGreaterThan(0);
    expect($pack->file_path)->toBeString()->not->toBeEmpty();
    expect($pack->file_disk)->toBe('exports');
    expect($pack->generated_at)->not->toBeNull();
    expect($pack->expires_at)->not->toBeNull();
    expect($pack->fingerprint)->toBeString()->not->toBeEmpty();
    expect($pack->summary)->toBeArray();
    expect($pack->summary['finding_count'])->toBe(4);
    expect($pack->summary['report_count'])->toBe(2);
    expect($pack->evidence_snapshot_id)->toBe((int) $snapshot->getKey());

    // File exists on disk
    Storage::disk('exports')->assertExists($pack->file_path);

    // OperationRun completed
    $opRun = OperationRun::query()->find($pack->operation_run_id);
    expect($opRun->status)->toBe(OperationRunStatus::Completed->value);
    expect($opRun->outcome)->toBe(OperationRunOutcome::Succeeded->value);

    // Notification sent (standard OperationRunCompleted via OperationRunService)
    Notification::assertSentTo($user, OperationRunCompleted::class);
});

it('seeds and advances counted progress while review pack files are written', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);
    Notification::fake();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user, [
        'include_pii' => true,
        'include_operations' => true,
    ]);

    $seededCounts = [];
    $increments = [];
    $realOperationRuns = app(OperationRunService::class);

    $spyOperationRuns = new class($realOperationRuns, $seededCounts, $increments) extends OperationRunService
    {
        private array $seededCounts;

        private array $increments;

        public function __construct(private readonly OperationRunService $inner, array &$seededCounts, array &$increments)
        {
            $this->seededCounts = &$seededCounts;
            $this->increments = &$increments;
        }

        public function updateRun(OperationRun $run, string $status, ?string $outcome = null, array $summaryCounts = [], array $failures = []): OperationRun
        {
            if ($status === OperationRunStatus::Running->value && $summaryCounts !== []) {
                $this->seededCounts[] = $summaryCounts;
            }

            return $this->inner->updateRun($run, $status, $outcome, $summaryCounts, $failures);
        }

        public function incrementSummaryCounts(OperationRun $run, array $delta): OperationRun
        {
            $this->increments[] = $delta;

            return $this->inner->incrementSummaryCounts($run, $delta);
        }
    };

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );

    $job->handle($spyOperationRuns);

    $pack->refresh();
    $operationRun = OperationRun::query()->findOrFail($pack->operation_run_id);

    expect($seededCounts)->toHaveCount(1)
        ->and($seededCounts[0])->toMatchArray([
            'total' => 7,
            'processed' => 0,
            'succeeded' => 0,
            'failed' => 0,
            'skipped' => 0,
        ])
        ->and($increments)->toHaveCount(7);

    foreach ($increments as $delta) {
        expect($delta)->toBe([
            'processed' => 1,
            'created' => 1,
        ]);
    }

    expect($pack->status)->toBe(ReviewPackStatus::Ready->value)
        ->and($operationRun->summary_counts ?? [])->toMatchArray([
            'total' => 7,
            'processed' => 7,
            'created' => 7,
            'finding_count' => (int) ($pack->summary['finding_count'] ?? 0),
            'report_count' => (int) ($pack->summary['report_count'] ?? 0),
            'operation_count' => (int) ($pack->summary['operation_count'] ?? 0),
        ]);

    expect($operationRun->summary_counts ?? [])->not->toHaveKeys([
        'data_freshness',
        'risk_acceptance',
        'evidence_resolution',
    ]);
});

it('does not send queued or terminal run notifications when suspended read-only blocks generation', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);
    suspendReviewPackGenerationWorkspaceForGenerationTest($tenant);

    Notification::fake();

    expect(fn (): ReviewPack => app(ReviewPackService::class)->generate($tenant, $user))
        ->toThrow(WorkspaceEntitlementBlockedException::class, 'suspended / read-only');

    Notification::assertNotSentTo($user, OperationRunQueued::class);
    Notification::assertNotSentTo($user, OperationRunCompleted::class);
});

// ─── Failure Path ──────────────────────────────────────────────

it('marks pack as failed when generation throws an exception', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    Notification::fake();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user);

    // Replace the exports disk with a mock that throws on put()
    $fakeDisk = Mockery::mock(\Illuminate\Contracts\Filesystem\Filesystem::class);
    $fakeDisk->shouldReceive('put')
        ->andThrow(new \RuntimeException('Simulated storage failure'));

    Storage::shouldReceive('disk')
        ->with('exports')
        ->andReturn($fakeDisk);

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );

    try {
        app()->call([$job, 'handle']);
    } catch (\RuntimeException) {
        // Expected — the job re-throws after marking failed
    }

    $pack->refresh();

    expect($pack->status)->toBe(ReviewPackStatus::Failed->value);

    $opRun = OperationRun::query()->find($pack->operation_run_id);
    expect($opRun->status)->toBe(OperationRunStatus::Completed->value);
    expect($opRun->outcome)->toBe(OperationRunOutcome::Failed->value);
    expect($opRun->failure_summary)->toBeArray();
    expect($opRun->failure_summary[0]['code'])->toBe('generation_error');

    Notification::assertSentTo($user, OperationRunCompleted::class);
});

// ─── Empty Reports ──────────────────────────────────────────────

it('fails explicitly when no evidence snapshot exists', function (): void {
    [$user, $tenant] = createUserWithTenant();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    expect(fn (): ReviewPack => $service->generate($tenant, $user))
        ->toThrow(ReviewPackEvidenceResolutionException::class, 'No eligible evidence snapshot is available');
});

it('fails explicitly when the latest evidence snapshot is ineligible', function (): void {
    [$user, $tenant] = createUserWithTenant();

    $snapshot = EvidenceSnapshot::query()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => EvidenceSnapshotStatus::Active->value,
        'completeness_state' => 'missing',
        'summary' => [],
        'generated_at' => now(),
    ]);

    $snapshot->items()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'dimension_key' => 'findings_summary',
        'state' => 'missing',
        'required' => true,
        'source_kind' => 'model_summary',
        'source_record_type' => Finding::class,
        'summary_payload' => [],
        'sort_order' => 10,
    ]);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    expect(fn (): ReviewPack => $service->generate($tenant, $user))
        ->toThrow(ReviewPackEvidenceResolutionException::class, 'latest evidence snapshot is not eligible');
});

// ─── PII Redaction ──────────────────────────────────────────────

it('redacts PII when include_pii is false', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);
    Notification::fake();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user, ['include_pii' => false]);

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );
    app()->call([$job, 'handle']);

    $pack->refresh();
    expect($pack->status)->toBe(ReviewPackStatus::Ready->value);

    // Read the generated ZIP to verify PII redaction
    $zipContent = Storage::disk('exports')->get($pack->file_path);
    $tempFile = tempnam(sys_get_temp_dir(), 'test-zip-');
    file_put_contents($tempFile, $zipContent);

    $zip = new ZipArchive;
    $zip->open($tempFile);

    // Check metadata.json redacts tenant name
    $metadata = json_decode($zip->getFromName('metadata.json'), true);
    expect($metadata['tenant_name'])->toBe('[REDACTED]');
    expect($metadata['options']['include_pii'])->toBeFalse();

    // Check findings.csv redacts title in rows
    $findingsCsv = $zip->getFromName('findings.csv');
    expect($findingsCsv)->toContain('[REDACTED]');

    // Check entra_admin_roles.json redacts displayName
    $entraReport = json_decode($zip->getFromName('reports/entra_admin_roles.json'), true);

    if (! empty($entraReport) && isset($entraReport['roles'])) {
        foreach ($entraReport['roles'] as $role) {
            if (isset($role['displayName'])) {
                expect($role['displayName'])->toBe('[REDACTED]');
            }
        }
    }

    $zip->close();
    unlink($tempFile);
});

// ─── ZIP Contents ──────────────────────────────────────────────

it('produces a ZIP with exactly 7 files in alphabetical order', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);
    Notification::fake();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user, [
        'include_pii' => true,
        'include_operations' => true,
    ]);

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );
    app()->call([$job, 'handle']);

    $pack->refresh();

    $zipContent = Storage::disk('exports')->get($pack->file_path);
    $tempFile = tempnam(sys_get_temp_dir(), 'test-zip-');
    file_put_contents($tempFile, $zipContent);

    $zip = new ZipArchive;
    $zip->open($tempFile);

    $files = [];
    for ($i = 0; $i < $zip->numFiles; $i++) {
        $files[] = $zip->getNameIndex($i);
    }

    $zip->close();
    unlink($tempFile);

    $expectedFiles = [
        'findings.csv',
        'hardening.json',
        'metadata.json',
        'operations.csv',
        'reports/entra_admin_roles.json',
        'reports/permission_posture.json',
        'summary.json',
    ];

    expect($files)->toHaveCount(7);
    expect($files)->toEqual($expectedFiles);
});

// ─── Service dispatches job ──────────────────────────────────

it('dispatches GenerateReviewPackJob when generate is called', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant();
    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user);

    Queue::assertPushed(GenerateReviewPackJob::class, function ($job) use ($pack) {
        return $job->reviewPackId === (int) $pack->getKey();
    });
});

it('does not send queued database notification when review pack generation is requested', function (): void {
    Queue::fake();
    Notification::fake();

    [$user, $tenant] = createUserWithTenant();
    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $service->generate($tenant, $user);

    Notification::assertNotSentTo($user, OperationRunQueued::class);
});

// ─── OperationRun Type ──────────────────────────────────────────

it('creates an OperationRun of type review_pack_generate', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant();
    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user);

    $opRun = OperationRun::query()->find($pack->operation_run_id);
    expect($opRun)->not->toBeNull();
    expect($opRun->type)->toBe(OperationRunType::ReviewPackGenerate->value);
    expect($opRun->status)->toBe(OperationRunStatus::Queued->value);
});

// ─── Fingerprint Determinism ──────────────────────────────────

it('computes the same fingerprint for identical inputs', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    $options = ['include_pii' => true, 'include_operations' => true];
    $fp1 = $service->computeFingerprint($tenant, $options);
    $fp2 = $service->computeFingerprint($tenant, $options);

    expect($fp1)->toBe($fp2);
    expect(strlen($fp1))->toBe(64); // SHA-256 hex length
});

// ─── Different options produce different fingerprints ─────────

it('computes different fingerprints when options differ', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    $fp1 = $service->computeFingerprint($tenant, ['include_pii' => true, 'include_operations' => true]);
    $fp2 = $service->computeFingerprint($tenant, ['include_pii' => false, 'include_operations' => true]);

    expect($fp1)->not->toBe($fp2);
});

// ─── Fingerprint Dedupe (T025) ────────────────────────────────

it('returns existing ready pack when fingerprint matches (dedupe)', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant();
    seedTenantWithData($tenant);
    $snapshot = createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    $options = ['include_pii' => true, 'include_operations' => true];

    // Compute the fingerprint that the service would compute with normalized options
    $fingerprint = $service->computeFingerprint($tenant, $options);

    $pack1 = $service->generate($tenant, $user, $options);

    // Manually set the pack to ready with the correct fingerprint so dedupe triggers
    $pack1->update([
        'status' => ReviewPackStatus::Ready->value,
        'fingerprint' => $fingerprint,
        'evidence_snapshot_id' => (int) $snapshot->getKey(),
        'expires_at' => now()->addDays(90),
    ]);

    // Second call with same options should return the existing pack
    $pack2 = $service->generate($tenant, $user, $options);

    expect($pack2->getKey())->toBe($pack1->getKey());
    expect(ReviewPack::query()->where('managed_environment_id', (int) $tenant->getKey())->count())->toBe(1);
});

it('allows new generation when existing pack with same fingerprint is expired', function (): void {
    Queue::fake();

    [$user, $tenant] = createUserWithTenant();
    seedTenantWithData($tenant);
    $snapshot = createEvidenceSnapshotForReviewPack($tenant);

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);

    $options = ['include_pii' => true, 'include_operations' => true];

    // Create an expired pack with a matching fingerprint
    $fingerprint = $service->computeFingerprint($tenant, ['include_pii' => true, 'include_operations' => true]);

    ReviewPack::factory()->expired()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'initiated_by_user_id' => (int) $user->getKey(),
        'evidence_snapshot_id' => (int) $snapshot->getKey(),
        'fingerprint' => $fingerprint,
    ]);

    // Should create a new pack since existing is expired
    $newPack = $service->generate($tenant, $user, $options);

    expect(ReviewPack::query()->where('managed_environment_id', (int) $tenant->getKey())->count())->toBe(2);
    expect($newPack->status)->toBe(ReviewPackStatus::Queued->value);
});

it('builds the review pack from snapshot payloads instead of live records', function (): void {
    [$user, $tenant] = createUserWithTenant();

    seedTenantWithData($tenant);
    $snapshot = createEvidenceSnapshotForReviewPack($tenant);
    Notification::fake();

    StoredReport::query()->where('managed_environment_id', (int) $tenant->getKey())->delete();
    Finding::query()->where('managed_environment_id', (int) $tenant->getKey())->delete();
    OperationRun::query()->where('managed_environment_id', (int) $tenant->getKey())->delete();

    /** @var ReviewPackService $service */
    $service = app(ReviewPackService::class);
    $pack = $service->generate($tenant, $user, [
        'include_pii' => true,
        'include_operations' => true,
    ]);

    $job = new GenerateReviewPackJob(
        reviewPackId: (int) $pack->getKey(),
        operationRunId: (int) $pack->operation_run_id,
    );
    app()->call([$job, 'handle']);

    $pack->refresh();

    expect($pack->status)->toBe(ReviewPackStatus::Ready->value)
        ->and($pack->evidence_snapshot_id)->toBe((int) $snapshot->getKey())
        ->and($pack->summary['finding_count'])->toBe((int) $snapshot->summary['finding_count'])
        ->and($pack->summary['report_count'])->toBe((int) $snapshot->summary['report_count']);

    Storage::disk('exports')->assertExists($pack->file_path);
});
