<?php

declare(strict_types=1);

use App\Filament\Resources\AlertDeliveryResource;
use App\Filament\Resources\BackupScheduleResource;
use App\Filament\Resources\BaselineProfileResource;
use App\Filament\Resources\BaselineProfileResource\Pages\ListBaselineProfiles;
use App\Filament\Resources\InventoryItemResource;
use App\Filament\Resources\InventoryItemResource\Pages\ListInventoryItems;
use App\Filament\Resources\OperationRunResource;
use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyResource\Pages\ListPolicies;
use App\Filament\Resources\PolicyResource\RelationManagers\VersionsRelationManager;
use App\Filament\Resources\Workspaces\WorkspaceResource;
use App\Jobs\SyncPoliciesJob;
use App\Models\BaselineProfile;
use App\Models\InventoryItem;
use App\Models\OperationRun;
use App\Models\Tenant;
use App\Support\OperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceExemptions;
use App\Support\Ui\ActionSurface\ActionSurfaceProfileDefinition;
use App\Support\Ui\ActionSurface\ActionSurfaceValidator;
use App\Support\Ui\ActionSurface\Enums\ActionSurfacePanelScope;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use Filament\Actions\ActionGroup;
use Filament\Actions\BulkActionGroup;
use Filament\Facades\Filament;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

uses(RefreshDatabase::class);

it('passes the action surface contract guard for current repository state', function (): void {
    $result = ActionSurfaceValidator::withBaselineExemptions()->validate();

    expect($result->hasIssues())->toBeFalse($result->formatForAssertion());
});

it('excludes widgets from action surface discovery scope', function (): void {
    $classes = array_map(
        static fn ($component): string => $component->className,
        ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents(),
    );

    $widgetClasses = array_values(array_filter($classes, static function (string $className): bool {
        return str_starts_with($className, 'App\\Filament\\Widgets\\');
    }));

    expect($widgetClasses)->toBeEmpty();
});

it('keeps baseline exemptions explicit and does not auto-exempt unknown classes', function (): void {
    $exemptions = ActionSurfaceExemptions::baseline();

    expect($exemptions->hasClass('App\\Filament\\Resources\\ActionSurfaceUnknownResource'))->toBeFalse();
});

it('maps tenant/admin panel scope metadata from discovery sources', function (): void {
    $components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
        ->keyBy('className');

    $tenantResource = $components->get(\App\Filament\Resources\TenantResource::class);
    $policyResource = $components->get(\App\Filament\Resources\PolicyResource::class);

    expect($tenantResource)->not->toBeNull();
    expect($tenantResource?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeTrue();

    expect($policyResource)->not->toBeNull();
    expect($policyResource?->hasPanelScope(ActionSurfacePanelScope::Tenant))->toBeTrue();
});

it('requires non-empty reasons for every baseline exemption', function (): void {
    $reasons = ActionSurfaceExemptions::baseline()->all();

    foreach ($reasons as $className => $reason) {
        expect(trim($reason))->not->toBe('', "Baseline exemption reason is empty for {$className}");
    }
});

it('discovers the baseline profile resource and validates its declaration', function (): void {
    $components = collect(ActionSurfaceValidator::withBaselineExemptions()->discoveredComponents())
        ->keyBy('className');

    $baselineResource = $components->get(BaselineProfileResource::class);

    expect($baselineResource)->not->toBeNull('BaselineProfileResource should be discovered by action surface discovery');
    expect($baselineResource?->hasPanelScope(ActionSurfacePanelScope::Admin))->toBeTrue();

    $declaration = BaselineProfileResource::actionSurfaceDeclaration();
    $profiles = new ActionSurfaceProfileDefinition;

    foreach ($profiles->requiredSlots($declaration->profile) as $slot) {
        expect($declaration->slot($slot))
            ->not->toBeNull("Missing required slot {$slot->value} in BaselineProfileResource declaration");
    }
});

it('keeps BaselineProfile archive under the More menu and declares it in the action surface slots', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

    $profile = BaselineProfile::factory()->active()->create([
        'workspace_id' => (int) $tenant->workspace_id,
    ]);

    $declaration = BaselineProfileResource::actionSurfaceDeclaration();
    $details = $declaration->slot(ActionSurfaceSlot::ListRowMoreMenu)?->details;

    expect($details)->toBeString();
    expect($details)->toContain('archive');

    $this->actingAs($user);

    $livewire = Livewire::test(ListBaselineProfiles::class)
        ->assertCanSeeTableRecords([$profile]);

    $table = $livewire->instance()->getTable();

    $rowActions = $table->getActions();
    $moreGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);

    expect($moreGroup)->toBeInstanceOf(ActionGroup::class);
    expect($moreGroup?->getLabel())->toBe('More');

    $primaryRowActionNames = collect($rowActions)
        ->reject(static fn ($action): bool => $action instanceof ActionGroup)
        ->map(static fn ($action): ?string => $action->getName())
        ->filter()
        ->values()
        ->all();

    expect($primaryRowActionNames)->toContain('view');
    expect($primaryRowActionNames)->not->toContain('archive');

    $primaryRowActionCount = count($primaryRowActionNames);
    expect($primaryRowActionCount)->toBeLessThanOrEqual(2);

    $moreActionNames = collect($moreGroup?->getActions())
        ->map(static fn ($action): ?string => $action->getName())
        ->filter()
        ->values()
        ->all();

    expect($moreActionNames)->toContain('archive');
});

it('ensures representative declarations satisfy required slots', function (): void {
    $profiles = new ActionSurfaceProfileDefinition;

    $declarations = [
        AlertDeliveryResource::class => AlertDeliveryResource::actionSurfaceDeclaration(),
        BackupScheduleResource::class => BackupScheduleResource::actionSurfaceDeclaration(),
        PolicyResource::class => PolicyResource::actionSurfaceDeclaration(),
        OperationRunResource::class => OperationRunResource::actionSurfaceDeclaration(),
        VersionsRelationManager::class => VersionsRelationManager::actionSurfaceDeclaration(),
        BaselineProfileResource::class => BaselineProfileResource::actionSurfaceDeclaration(),
        WorkspaceResource::class => WorkspaceResource::actionSurfaceDeclaration(),
    ];

    foreach ($declarations as $className => $declaration) {
        foreach ($profiles->requiredSlots($declaration->profile) as $slot) {
            expect($declaration->slot($slot))
                ->not->toBeNull("Missing required slot {$slot->value} in declaration for {$className}");
        }
    }
});

it('documents the guided alert delivery empty state without introducing a list-header CTA', function (): void {
    $declaration = AlertDeliveryResource::actionSurfaceDeclaration();

    expect((string) ($declaration->slot(ActionSurfaceSlot::ListEmptyState)?->details ?? ''))
        ->toContain('View alert rules');
});

it('uses More grouping conventions and exposes empty-state CTA on representative CRUD list', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $livewire = Livewire::test(ListPolicies::class)
        ->assertTableEmptyStateActionsExistInOrder(['sync']);

    $table = $livewire->instance()->getTable();

    $rowActions = $table->getActions();
    $rowGroup = collect($rowActions)->first(static fn ($action): bool => $action instanceof ActionGroup);

    expect($rowGroup)->toBeInstanceOf(ActionGroup::class);
    expect($rowGroup?->getLabel())->toBe('More');

    $primaryRowActionCount = collect($rowActions)
        ->reject(static fn ($action): bool => $action instanceof ActionGroup)
        ->count();

    expect($primaryRowActionCount)->toBeLessThanOrEqual(2);

    $bulkActions = $table->getBulkActions();
    $bulkGroup = collect($bulkActions)->first(static fn ($action): bool => $action instanceof BulkActionGroup);

    expect($bulkGroup)->toBeInstanceOf(BulkActionGroup::class);
    expect($bulkGroup?->getLabel())->toBe('More');
});

it('uses canonical tenantless View run links on representative operation links', function (): void {
    $tenant = Tenant::factory()->create();
    $run = OperationRun::factory()->create([
        'tenant_id' => $tenant->getKey(),
        'workspace_id' => $tenant->workspace_id,
    ]);

    expect(OperationRunLinks::view($run, $tenant))
        ->toBe(route('admin.operations.view', ['run' => (int) $run->getKey()]));
});

it('removes lone View buttons and uses clickable rows on the inventory items list', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    $item = InventoryItem::factory()->create([
        'tenant_id' => $tenant->getKey(),
    ]);

    $livewire = Livewire::test(ListInventoryItems::class);
    $table = $livewire->instance()->getTable();

    expect($table->getActions())->toBeEmpty();

    $recordUrl = $table->getRecordUrl($item);

    expect($recordUrl)->not->toBeNull();
    expect($recordUrl)->toBe(InventoryItemResource::getUrl('view', ['record' => $item]));
});

it('keeps representative operation-start actions observable with actor and scope metadata', function (): void {
    Queue::fake();
    bindFailHardGraphClient();

    [$user, $tenant] = createUserWithTenant(role: 'owner');

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

    Livewire::test(ListPolicies::class)
        ->mountAction('sync')
        ->callMountedAction()
        ->assertHasNoActionErrors();

    Queue::assertPushed(SyncPoliciesJob::class);

    $run = OperationRun::query()
        ->where('tenant_id', $tenant->getKey())
        ->where('type', 'policy.sync')
        ->latest('id')
        ->first();

    expect($run)->not->toBeNull();
    expect((int) $run?->tenant_id)->toBe((int) $tenant->getKey());
    expect((int) $run?->workspace_id)->toBe((int) $tenant->workspace_id);
    expect((string) $run?->initiator_name)->toBe((string) $user->name);
});
