<?php

declare(strict_types=1);

use App\Filament\Pages\Monitoring\FindingExceptionsQueue;
use App\Filament\Resources\FindingExceptionResource\Pages\ViewFindingException;
use App\Filament\Resources\FindingResource\Pages\ViewFinding;
use App\Models\AuditLog;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\FindingExceptionDecision;
use App\Models\User;
use App\Services\Findings\FindingExceptionService;
use App\Support\Audit\AuditActionId;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

/**
 * @return array{0: User, 1: User, 2: \App\Models\ManagedEnvironment, 3: Finding, 4: FindingExceptionService, 5: FindingException}
 */
function seedApprovedFindingExceptionWindow(): array
{
    [$requester, $tenant] = createUserWithTenant(role: 'owner');
    $approver = User::factory()->create();
    createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager');

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

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

    $requested = $service->request($finding, $tenant, $requester, [
        'owner_user_id' => (int) $requester->getKey(),
        'request_reason' => 'Initial acceptance window',
        'review_due_at' => now()->addDays(5)->toDateTimeString(),
        'expires_at' => now()->addDays(20)->toDateTimeString(),
    ]);

    $active = $service->approve($requested, $approver, [
        'effective_from' => now()->subDay()->toDateTimeString(),
        'expires_at' => now()->addDays(20)->toDateTimeString(),
        'approval_reason' => 'Initial approval',
    ]);

    return [$requester, $approver, $tenant, $finding, $service, $active];
}

it('requests and approves a renewal while preserving prior decision history and evidence references', function (): void {
    [$requester, $tenant] = createUserWithTenant(role: 'owner');
    $approver = User::factory()->create();
    createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner', workspaceRole: 'manager');

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

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

    $requested = $service->request($finding, $tenant, $requester, [
        'owner_user_id' => (int) $requester->getKey(),
        'request_reason' => 'Initial compensating controls approved until maintenance window',
        'review_due_at' => now()->addDays(7)->toDateTimeString(),
        'expires_at' => now()->addDays(14)->toDateTimeString(),
        'evidence_references' => [
            [
                'label' => 'Initial baseline review',
                'source_type' => 'evidence_snapshot',
                'source_id' => 'snapshot-001',
                'source_fingerprint' => 'fp-initial-001',
                'measured_at' => now()->subDay()->toDateTimeString(),
                'summary_payload' => ['status' => 'pending_remediation'],
            ],
        ],
    ]);

    $service->approve($requested, $approver, [
        'effective_from' => now()->subDay()->toDateTimeString(),
        'expires_at' => now()->addDays(14)->toDateTimeString(),
        'approval_reason' => 'Approved until remediation can be scheduled.',
    ]);

    $this->actingAs($requester);
    Filament::setTenant($tenant, true);

    Livewire::test(ViewFinding::class, ['record' => $finding->getKey()])
        ->assertActionVisible('renew_exception')
        ->callAction('renew_exception', data: [
            'owner_user_id' => (int) $requester->getKey(),
            'request_reason' => 'Renew for the next maintenance window with updated rollback proof.',
            'review_due_at' => now()->addDays(10)->toDateTimeString(),
            'expires_at' => now()->addDays(45)->toDateTimeString(),
            'evidence_references' => [
                [
                    'label' => 'Rollback drill 2026-03-18',
                    'source_type' => 'review_pack',
                    'source_id' => 'rp-2026-03-18',
                    'source_fingerprint' => 'fp-renew-001',
                    'measured_at' => now()->subHours(12)->toDateTimeString(),
                ],
                [
                    'label' => 'Compensating controls review',
                    'source_type' => 'evidence_snapshot',
                    'source_id' => 'snapshot-002',
                    'source_fingerprint' => 'fp-renew-002',
                    'measured_at' => now()->subHours(6)->toDateTimeString(),
                ],
            ],
        ])
        ->assertHasNoActionErrors();

    $pendingRenewal = FindingException::query()
        ->with(['currentDecision', 'decisions', 'evidenceReferences'])
        ->where('finding_id', (int) $finding->getKey())
        ->firstOrFail();

    expect($pendingRenewal->status)->toBe(FindingException::STATUS_PENDING)
        ->and($pendingRenewal->currentDecision?->decision_type)->toBe(FindingExceptionDecision::TYPE_RENEWAL_REQUESTED)
        ->and($pendingRenewal->decisions->pluck('decision_type')->all())->toBe([
            FindingExceptionDecision::TYPE_REQUESTED,
            FindingExceptionDecision::TYPE_APPROVED,
            FindingExceptionDecision::TYPE_RENEWAL_REQUESTED,
        ])
        ->and($pendingRenewal->evidenceReferences)->toHaveCount(2)
        ->and($pendingRenewal->evidenceReferences->pluck('label')->all())->toBe([
            'Rollback drill 2026-03-18',
            'Compensating controls review',
        ])
        ->and($pendingRenewal->evidenceReferences->first()?->summary_payload)->toBe([]);

    $this->actingAs($approver);
    Filament::setCurrentPanel('admin');
    Filament::setTenant(null, true);
    Filament::bootCurrentPanel();

    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::withQueryParams([
        'exception' => (int) $pendingRenewal->getKey(),
    ])
        ->test(FindingExceptionsQueue::class)
        ->assertActionVisible('approve_selected_exception')
        ->callAction('approve_selected_exception', data: [
            'effective_from' => now()->addDays(14)->toDateTimeString(),
            'expires_at' => now()->addDays(45)->toDateTimeString(),
            'approval_reason' => 'Renewed while remediation remains scheduled and evidenced.',
        ])
        ->assertHasNoActionErrors()
        ->assertNotified('Exception renewed');

    $renewed = $pendingRenewal->fresh(['currentDecision', 'decisions', 'evidenceReferences']);

    expect($renewed?->status)->toBe(FindingException::STATUS_ACTIVE)
        ->and($renewed?->current_validity_state)->toBe(FindingException::VALIDITY_VALID)
        ->and($renewed?->currentDecision?->decision_type)->toBe(FindingExceptionDecision::TYPE_RENEWED)
        ->and($renewed?->decisions->pluck('decision_type')->all())->toBe([
            FindingExceptionDecision::TYPE_REQUESTED,
            FindingExceptionDecision::TYPE_APPROVED,
            FindingExceptionDecision::TYPE_RENEWAL_REQUESTED,
            FindingExceptionDecision::TYPE_RENEWED,
        ])
        ->and($finding->fresh()?->status)->toBe(Finding::STATUS_RISK_ACCEPTED)
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionRenewalRequested->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $pendingRenewal->getKey())
            ->exists())->toBeTrue()
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionRenewed->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $pendingRenewal->getKey())
            ->exists())->toBeTrue();

    $this->actingAs($requester);
    Filament::setTenant($tenant, true);

    Livewire::test(ViewFindingException::class, ['record' => $pendingRenewal->getKey()])
        ->assertOk()
        ->assertSee('Rollback drill 2026-03-18')
        ->assertSee('Compensating controls review')
        ->assertSee('Renewed while remediation remains scheduled and evidenced.');
});

it('rejects a pending renewal without erasing the prior approved governance window', function (): void {
    [$requester, $approver, $tenant, $finding, $service, $active] = seedApprovedFindingExceptionWindow();

    $service->renew($active, $requester, [
        'owner_user_id' => (int) $requester->getKey(),
        'request_reason' => 'Need additional time while vendor patch is validated.',
        'review_due_at' => now()->addDays(8)->toDateTimeString(),
        'expires_at' => now()->addDays(40)->toDateTimeString(),
        'evidence_references' => [
            [
                'label' => 'Patch validation evidence',
                'source_type' => 'review_pack',
            ],
        ],
    ]);

    $rejected = $service->reject($active->fresh(['currentDecision']), $approver, [
        'rejection_reason' => 'Renewal denied until stronger mitigation evidence is attached.',
    ]);

    expect($rejected->status)->toBe(FindingException::STATUS_ACTIVE)
        ->and($rejected->current_validity_state)->toBe(FindingException::VALIDITY_VALID)
        ->and($rejected->currentDecision?->decision_type)->toBe(FindingExceptionDecision::TYPE_REJECTED)
        ->and($rejected->decisions->pluck('decision_type')->all())->toBe([
            FindingExceptionDecision::TYPE_REQUESTED,
            FindingExceptionDecision::TYPE_APPROVED,
            FindingExceptionDecision::TYPE_RENEWAL_REQUESTED,
            FindingExceptionDecision::TYPE_REJECTED,
        ])
        ->and($rejected->rejection_reason)->toBe('Renewal denied until stronger mitigation evidence is attached.')
        ->and($finding->fresh()?->status)->toBe(Finding::STATUS_RISK_ACCEPTED);
});
