<?php

declare(strict_types=1);

use App\Filament\Pages\Monitoring\AuditLog as AuditLogPage;
use App\Filament\Pages\Monitoring\FindingExceptionsQueue;
use App\Filament\Pages\Monitoring\Operations;
use App\Filament\Resources\AlertDeliveryResource\Pages\ListAlertDeliveries;
use App\Filament\Resources\BackupScheduleResource\Pages\ListBackupSchedules;
use App\Filament\Resources\BackupSetResource\Pages\ListBackupSets;
use App\Filament\Resources\BaselineSnapshotResource\Pages\ListBaselineSnapshots;
use App\Filament\Resources\EntraGroupResource\Pages\ListEntraGroups;
use App\Filament\Resources\FindingResource\Pages\ListFindings;
use App\Filament\Resources\InventoryItemResource\Pages\ListInventoryItems;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ListManagedEnvironments;
use App\Filament\Resources\PolicyResource\Pages\ListPolicies;
use App\Filament\Resources\PolicyVersionResource\Pages\ListPolicyVersions;
use App\Filament\Resources\ProviderConnectionResource\Pages\ListProviderConnections;
use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns;
use App\Models\AuditLog;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Support\Workspaces\WorkspaceContext;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

/**
 * @param  array<string, mixed>  $parameters
 */
function spec125AssertPersistedTableState(
    string $componentClass,
    array $parameters,
    string $search,
    string $sortColumn,
    string $sortDirection,
    string $filterPath,
    mixed $filterValue,
    array $queryParams = [],
): void {
    $component = Livewire::withQueryParams($queryParams)
        ->test($componentClass, $parameters)
        ->searchTable($search)
        ->call('sortTable', $sortColumn, $sortDirection)
        ->set($filterPath, $filterValue);

    $instance = $component->instance();

    expect(session()->get($instance->getTableSearchSessionKey()))->toBe($search);
    expect(session()->get($instance->getTableSortSessionKey()))->toBe("{$sortColumn}:{$sortDirection}");
    expect(data_get(session()->get($instance->getTableFiltersSessionKey()), str($filterPath)->after('tableFilters.')->value()))->toBe($filterValue);

    Livewire::withQueryParams($queryParams)
        ->test($componentClass, $parameters)
        ->assertSet('tableSearch', $search)
        ->assertSet('tableSort', "{$sortColumn}:{$sortDirection}")
        ->assertSet($filterPath, $filterValue);
}

it('persists tenant list search, sort, and filter state across remounts', function (): void {
    [$user] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext();

    spec125AssertPersistedTableState(
        ListManagedEnvironments::class,
        [],
        'ManagedEnvironment',
        'name',
        'desc',
        'tableFilters.environment.value',
        'prod',
    );
});

it('persists policy list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListPolicies::class,
        [],
        'Policy',
        'display_name',
        'desc',
        'tableFilters.visibility.value',
        'active',
    );
});

it('persists backup-set list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListBackupSets::class,
        [],
        'Backup',
        'name',
        'desc',
        'tableFilters.trashed.value',
        1,
    );
});

it('persists backup-schedule list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListBackupSchedules::class,
        [],
        'Schedule',
        'name',
        'desc',
        'tableFilters.enabled_state.value',
        'enabled',
    );
});

it('persists provider-connections list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListProviderConnections::class,
        [],
        'Contoso',
        'display_name',
        'desc',
        'tableFilters.default_only.isActive',
        true,
    );
});

it('persists findings list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'manager');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListFindings::class,
        [],
        'drift',
        'created_at',
        'asc',
        'tableFilters.status.value',
        'new',
    );
});

it('persists inventory item list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListInventoryItems::class,
        [],
        'Policy',
        'display_name',
        'asc',
        'tableFilters.platform.value',
        'windows',
    );
});

it('persists policy version list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListPolicyVersions::class,
        [],
        'Policy',
        'captured_at',
        'asc',
        'tableFilters.platform.value',
        'windows',
    );
});

it('persists restore run list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListRestoreRuns::class,
        [],
        'Restore',
        'started_at',
        'asc',
        'tableFilters.status.value',
        'completed',
    );
});

it('persists alert delivery list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListAlertDeliveries::class,
        [],
        'alert',
        'created_at',
        'asc',
        'tableFilters.status.value',
        'sent',
    );
});

it('persists Entra group list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        ListEntraGroups::class,
        [],
        'Group',
        'display_name',
        'desc',
        'tableFilters.group_type.value',
        'security',
    );
});

it('persists baseline snapshot list search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);
    session([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id]);

    spec125AssertPersistedTableState(
        ListBaselineSnapshots::class,
        [],
        'Baseline',
        'captured_at',
        'asc',
        'tableFilters.snapshot_state.value',
        'with_gaps',
    );
});

it('persists monitoring operations search, sort, and filter state across remounts', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        Operations::class,
        [],
        'policy',
        'type',
        'desc',
        'tableFilters.status.value',
        'queued',
    );
});

it('restores operations table state while the requested tab stays query-driven', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext($tenant);

    spec125AssertPersistedTableState(
        Operations::class,
        [],
        'baseline',
        'created_at',
        'desc',
        'tableFilters.status.value',
        'failed',
        ['activeTab' => 'failed'],
    );

    Livewire::withQueryParams(['activeTab' => 'failed'])
        ->actingAs($user)
        ->test(Operations::class)
        ->assertSet('activeTab', 'failed');
});

it('clears selected audit event state when persisted filters no longer contain the record', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $selectedAudit = AuditLog::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'actor_email' => 'owner@example.com',
        'actor_name' => 'Owner',
        'actor_type' => 'human',
        'action' => 'workspace.selected',
        'status' => 'success',
        'resource_type' => 'workspace',
        'resource_id' => (string) $tenant->workspace_id,
        'target_label' => 'Workspace '.$tenant->workspace_id,
        'summary' => 'Selected audit event',
        'recorded_at' => now(),
    ]);

    AuditLog::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'actor_email' => 'owner@example.com',
        'actor_name' => 'Owner',
        'actor_type' => 'human',
        'action' => 'operation_run.failed',
        'status' => 'failure',
        'resource_type' => 'operation_run',
        'resource_id' => '1',
        'target_label' => 'Run #1',
        'summary' => 'Failure event',
        'recorded_at' => now()->addSecond(),
    ]);

    $this->actingAs($user);
    setAdminPanelContext();
    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    $auditComponent = Livewire::withQueryParams(['event' => (int) $selectedAudit->getKey()])
        ->actingAs($user)
        ->test(AuditLogPage::class);

    session()->put($auditComponent->instance()->getTableFiltersSessionKey(), [
        'outcome' => ['value' => 'failure'],
    ]);

    Livewire::withQueryParams(['event' => (int) $selectedAudit->getKey()])
        ->actingAs($user)
        ->test(AuditLogPage::class)
        ->assertSet('selectedAuditLogId', null);
});

it('clears selected exception state when persisted queue filters no longer contain the record', function (): void {
    [$approver, $tenant] = createUserWithTenant(role: 'owner', workspaceRole: 'manager');

    $finding = Finding::factory()->for($tenant)->create();

    $selectedException = FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $finding->getKey(),
        'requested_by_user_id' => (int) $approver->getKey(),
        'owner_user_id' => (int) $approver->getKey(),
        'status' => FindingException::STATUS_PENDING,
        'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
        'request_reason' => 'Selected queue exception',
        'requested_at' => now()->subDay(),
        'review_due_at' => now()->addDay(),
        'evidence_summary' => ['reference_count' => 0],
    ]);

    $rejectedFinding = Finding::factory()->for($tenant)->create();

    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $rejectedFinding->getKey(),
        'requested_by_user_id' => (int) $approver->getKey(),
        'owner_user_id' => (int) $approver->getKey(),
        'status' => FindingException::STATUS_REJECTED,
        'current_validity_state' => FindingException::VALIDITY_REJECTED,
        'request_reason' => 'Rejected queue exception',
        'requested_at' => now()->subDay(),
        'rejected_at' => now()->subHour(),
        'rejection_reason' => 'No longer needed',
        'evidence_summary' => ['reference_count' => 0],
    ]);

    $this->actingAs($approver);
    setAdminPanelContext();
    session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id);

    $queueComponent = Livewire::withQueryParams(['exception' => (int) $selectedException->getKey()])
        ->test(FindingExceptionsQueue::class);

    session()->put($queueComponent->instance()->getTableFiltersSessionKey(), [
        'status' => ['value' => FindingException::STATUS_REJECTED],
    ]);

    Livewire::withQueryParams(['exception' => (int) $selectedException->getKey()])
        ->test(FindingExceptionsQueue::class)
        ->assertSet('selectedFindingExceptionId', null);
});

it('reseeds the provider-connections tenant filter when the remembered admin tenant changes', function (): void {
    $tenantA = ManagedEnvironment::factory()->create();
    [$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner');
    $tenantB = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $tenantA->workspace_id,
    ]);
    createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner');

    $this->actingAs($user);
    setAdminPanelContext();

    $workspaceId = (int) $tenantA->workspace_id;

    session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
    session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
        (string) $workspaceId => (int) $tenantA->getKey(),
    ]);

    Livewire::actingAs($user)->test(ListProviderConnections::class)
        ->assertSet('tableFilters.tenant.value', (string) $tenantA->external_id);

    session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
        (string) $workspaceId => (int) $tenantB->getKey(),
    ]);

    Livewire::actingAs($user)->test(ListProviderConnections::class)
        ->assertSet('tableFilters.tenant.value', (string) $tenantB->external_id);
});
