<?php

use App\Filament\Resources\PolicyResource;
use App\Filament\Resources\PolicyResource\Pages\ListPolicies;
use App\Filament\Resources\ManagedEnvironmentResource\Pages\ListManagedEnvironments as ListManagedEnvironmentsPage;
use App\Models\OperationRun;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Illuminate\Support\Facades\Queue;
use Livewire\Livewire;

/**
 * Tests for US2: Non-members cannot infer tenant resources
 *
 * These tests verify that UiEnforcement correctly handles:
 * - Non-members → action hidden in UI (prevents discovery)
 * - Non-members → action blocked from execution (no side effects)
 * - Membership revoked mid-session → still enforces protection
 *
 * Note on 404 behavior:
 * In Filament v5, hidden actions are treated as disabled and return 200 (no execution)
 * rather than 404. This is because Filament's action system doesn't support custom
 * HTTP status codes for blocked actions. The security guarantee is:
 * - Non-members cannot discover actions (hidden in UI)
 * - Non-members cannot execute actions (blocked by Filament's isHidden check)
 * - No side effects occur (jobs not pushed, data not modified)
 *
 * True 404 enforcement happens at the page/routing level via tenant middleware.
 */
describe('US2: Non-member sees action hidden in UI', function () {
    beforeEach(function () {
        Queue::fake();
    });

    it('hides non-member tenants from the tenant management list before lifecycle actions can be discovered', function (): void {
        $visibleTenant = ManagedEnvironment::factory()->active()->create();
        $hiddenTenant = ManagedEnvironment::factory()->archived()->create([
            'workspace_id' => (int) $visibleTenant->workspace_id,
        ]);

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

        Filament::setTenant($visibleTenant, true);

        Livewire::actingAs($user)
            ->test(ListManagedEnvironmentsPage::class)
            ->assertCanSeeTableRecords([$visibleTenant])
            ->assertCanNotSeeTableRecords([$hiddenTenant]);

        $this->actingAs($user)
            ->get(PolicyResource::getUrl('index', tenant: $hiddenTenant))
            ->assertNotFound();

        Queue::assertNothingPushed();
    });

    it('hides sync action for users who are not members of the tenant', function () {
        $tenant = ManagedEnvironment::factory()->create();
        $otherTenant = ManagedEnvironment::factory()->create();

        // Create user with a valid workspace context, but without membership to $tenant
        [$user] = createUserWithTenant(tenant: $otherTenant, role: 'owner');

        $this->actingAs($user)
            ->get(PolicyResource::getUrl('index', tenant: $tenant))
            ->assertNotFound();

        Queue::assertNothingPushed();
    });

    it('hides sync action for authenticated users accessing wrong tenant', function () {
        // User is member of tenantA but accessing tenantB
        [$user, $tenantA] = createUserWithTenant(role: 'owner');
        $tenantB = ManagedEnvironment::factory()->create();
        // User has no membership to tenantB

        $this->actingAs($user)
            ->get(PolicyResource::getUrl('index', tenant: $tenantB))
            ->assertNotFound();

        Queue::assertNothingPushed();
    });
});

describe('US2: Non-member action execution is blocked', function () {
    beforeEach(function () {
        Queue::fake();
    });

    it('blocks action execution for non-members (no side effects)', function () {
        $tenant = ManagedEnvironment::factory()->create();
        $otherTenant = ManagedEnvironment::factory()->create();

        // Create user with a valid workspace context, but without membership to $tenant
        [$user] = createUserWithTenant(tenant: $otherTenant, role: 'owner');
        // No membership

        $this->actingAs($user)
            ->get(PolicyResource::getUrl('index', tenant: $tenant))
            ->assertNotFound();

        // Verify no side effects
        Queue::assertNothingPushed();
        expect(OperationRun::query()->where('managed_environment_id', $tenant->getKey())->count())->toBe(0);
    });
});

describe('US2: Membership revoked mid-session still enforces protection', function () {
    beforeEach(function () {
        Queue::fake();
    });

    it('blocks action execution when membership is revoked between page load and action click', function () {
        bindFailHardGraphClient();

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

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

        // Start the test - action should be visible for member
        $component = Livewire::actingAs($user)
            ->test(ListPolicies::class)
            ->assertActionVisible('sync')
            ->assertActionEnabled('sync');

        // Simulate membership revocation mid-session
        $user->tenants()->detach($tenant->getKey());
        $user->workspaces()->detach((int) $tenant->workspace_id);

        // Clear capability cache to ensure fresh check
        app(\App\Services\Auth\CapabilityResolver::class)->clearCache();
        app(\App\Services\Auth\WorkspaceCapabilityResolver::class)->clearCache();

        // Now try to execute - action is now hidden (via fresh isVisible evaluation)
        // Filament blocks execution (returns 200 but no side effects)
        $component
            ->mountAction('sync')
            ->callMountedAction()
            ->assertSuccessful();

        // Verify no side effects
        Queue::assertNothingPushed();
        expect(OperationRun::query()->where('managed_environment_id', $tenant->getKey())->count())->toBe(0);
    });

    it('hides action in UI after membership revocation on re-render', function () {
        [$user, $tenant] = createUserWithTenant(role: 'owner');

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

        // Initial state - action visible
        Livewire::actingAs($user)
            ->test(ListPolicies::class)
            ->assertActionVisible('sync');

        // Revoke membership
        $user->tenants()->detach($tenant->getKey());
        $user->workspaces()->detach((int) $tenant->workspace_id);
        app(\App\Services\Auth\CapabilityResolver::class)->clearCache();
        app(\App\Services\Auth\WorkspaceCapabilityResolver::class)->clearCache();

        // New request (simulates page refresh) should now be tenant-denied
        $this->actingAs($user)
            ->get(PolicyResource::getUrl('index', tenant: $tenant))
            ->assertNotFound();

        Queue::assertNothingPushed();
    });
});
