<?php

declare(strict_types=1);

use App\Jobs\GenerateReviewPackJob;
use App\Models\EvidenceSnapshot;
use App\Models\Finding;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\User;
use App\Services\Evidence\EvidenceSnapshotService;
use App\Services\Findings\FindingExceptionService;
use App\Services\Findings\FindingRiskGovernanceResolver;
use App\Services\ReviewPackService;
use App\Support\Evidence\EvidenceSnapshotStatus;
use App\Support\OperationRunType;
use App\Support\ReviewPackStatus;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Storage;

uses(RefreshDatabase::class);

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

it('stores only valid governed accepted risk in review pack summaries and exports', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $approver = User::factory()->create();
    createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager');

    StoredReport::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'report_type' => StoredReport::REPORT_TYPE_PERMISSION_POSTURE,
    ]);

    StoredReport::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'report_type' => StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES,
    ]);

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

    /** @var FindingExceptionService $exceptionService */
    $exceptionService = app(FindingExceptionService::class);

    $createApprovedException = function (Finding $finding, string $expiresAt) use ($exceptionService, $user, $tenant, $approver): Finding {
        $requested = $exceptionService->request($finding, $tenant, $user, [
            'owner_user_id' => (int) $user->getKey(),
            'request_reason' => 'Temporary exception',
            'review_due_at' => now()->addDays(5)->toDateTimeString(),
            'expires_at' => now()->addDays(14)->toDateTimeString(),
        ]);

        $exceptionService->approve($requested, $approver, [
            'effective_from' => now()->subDays(10)->toDateTimeString(),
            'expires_at' => $expiresAt,
            'approval_reason' => 'Approved with controls',
        ]);

        return $finding;
    };

    $createApprovedException(
        Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]),
        now()->addDays(14)->toDateTimeString(),
    );

    $expiredFinding = $createApprovedException(
        Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]),
        now()->subDay()->toDateTimeString(),
    );
    app(FindingRiskGovernanceResolver::class)->syncExceptionState($expiredFinding->findingException()->firstOrFail());

    $revokedFinding = $createApprovedException(
        Finding::factory()->for($tenant)->create(['status' => Finding::STATUS_RISK_ACCEPTED]),
        now()->addDays(14)->toDateTimeString(),
    );
    $exceptionService->revoke($revokedFinding->findingException()->firstOrFail(), $user, [
        'revocation_reason' => 'Controls removed',
    ]);

    Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_RISK_ACCEPTED,
    ]);

    /** @var EvidenceSnapshotService $snapshotService */
    $snapshotService = app(EvidenceSnapshotService::class);
    $payload = $snapshotService->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'],
        ]);
    }

    /** @var ReviewPackService $reviewPackService */
    $reviewPackService = app(ReviewPackService::class);
    $pack = $reviewPackService->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)->toBeInstanceOf(ReviewPack::class)
        ->and($pack->status)->toBe(ReviewPackStatus::Ready->value)
        ->and($pack->summary['risk_acceptance'] ?? null)->toBe([
            'status_marked_count' => 4,
            'valid_governed_count' => 1,
            'warning_count' => 3,
            'expired_count' => 1,
            'revoked_count' => 1,
            'missing_exception_count' => 1,
        ]);

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

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

    $summary = json_decode((string) $zip->getFromName('summary.json'), true, 512, JSON_THROW_ON_ERROR);

    expect($summary['risk_acceptance'] ?? null)->toBe([
        'status_marked_count' => 4,
        'valid_governed_count' => 1,
        'warning_count' => 3,
        'expired_count' => 1,
        'revoked_count' => 1,
        'missing_exception_count' => 1,
    ]);

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