<?php

declare(strict_types=1);

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

uses(RefreshDatabase::class);

it('requests and approves a finding exception through the finding view and canonical queue', function (): void {
    [$requester, $tenant] = createUserWithTenant(role: 'owner');
    $approver = \App\Models\User::factory()->create();
    createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner');

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

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

    Livewire::test(ViewFinding::class, ['record' => $finding->getKey()])
        ->callAction('request_exception', [
            'owner_user_id' => (int) $requester->getKey(),
            'request_reason' => 'Awaiting remediation window',
            'review_due_at' => now()->addDays(7)->toDateTimeString(),
            'expires_at' => now()->addDays(30)->toDateTimeString(),
        ])
        ->assertHasNoActionErrors();

    $exception = FindingException::query()->where('finding_id', (int) $finding->getKey())->first();

    expect($exception)->toBeInstanceOf(FindingException::class)
        ->and($exception?->status)->toBe(FindingException::STATUS_PENDING);

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

    session()->put(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::withQueryParams([
        'exception' => (int) $exception?->getKey(),
    ])
        ->test(FindingExceptionsQueue::class)
        ->assertActionVisible('approve_selected_exception')
        ->assertSee('Awaiting remediation window');

    $pendingException = FindingException::query()->findOrFail((int) $exception?->getKey());

    app(FindingExceptionService::class)->approve($pendingException, $approver, [
        'effective_from' => now()->addHour()->toDateTimeString(),
        'expires_at' => now()->addDays(30)->toDateTimeString(),
        'approval_reason' => 'Approved with compensating controls',
    ]);

    expect($exception?->fresh()?->status)->toBe(FindingException::STATUS_ACTIVE)
        ->and($finding->fresh()?->status)->toBe(Finding::STATUS_RISK_ACCEPTED)
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionRequested->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $exception?->getKey())
            ->exists())->toBeTrue()
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionApproved->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $exception?->getKey())
            ->exists())->toBeTrue();
});

it('requests and rejects a finding exception while keeping the finding out of accepted risk', function (): void {
    [$requester, $tenant] = createUserWithTenant(role: 'owner');
    $approver = \App\Models\User::factory()->create();
    createUserWithTenant(tenant: $tenant, user: $approver, role: 'owner');

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

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

    Livewire::test(ViewFinding::class, ['record' => $finding->getKey()])
        ->callAction('request_exception', [
            'owner_user_id' => (int) $requester->getKey(),
            'request_reason' => 'Awaiting vendor remediation timeline',
            'review_due_at' => now()->addDays(7)->toDateTimeString(),
            'expires_at' => now()->addDays(21)->toDateTimeString(),
        ])
        ->assertHasNoActionErrors();

    $exception = FindingException::query()->where('finding_id', (int) $finding->getKey())->firstOrFail();

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

    session()->put(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    Livewire::withQueryParams([
        'exception' => (int) $exception->getKey(),
    ])
        ->test(FindingExceptionsQueue::class)
        ->assertActionVisible('reject_selected_exception')
        ->assertSee('Awaiting vendor remediation timeline');

    app(FindingExceptionService::class)->reject($exception->fresh(), $approver, [
        'rejection_reason' => 'Remediation must be completed before acceptance is granted.',
    ]);

    expect($exception->fresh()?->status)->toBe(FindingException::STATUS_REJECTED)
        ->and($finding->fresh()?->status)->toBe(Finding::STATUS_NEW)
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionRequested->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $exception->getKey())
            ->exists())->toBeTrue()
        ->and(AuditLog::query()
            ->where('action', AuditActionId::FindingExceptionRejected->value)
            ->where('resource_type', 'finding_exception')
            ->where('resource_id', (string) $exception->getKey())
            ->exists())->toBeTrue();
});
