<?php

declare(strict_types=1);

use App\Filament\Pages\Monitoring\Operations;
use App\Filament\Resources\RestoreRunResource\Pages\ListRestoreRuns;
use App\Models\BackupSet;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\RestoreRun;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Livewire\Livewire;

uses(\Illuminate\Foundation\Testing\RefreshDatabase::class);

test('operation runs remain workspace-wide unless an explicit environment filter is set', function (): void {
    $tenantA = ManagedEnvironment::factory()->create();
    [$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner');

    $tenantB = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $tenantA->workspace_id,
    ]);

    $user->tenants()->syncWithoutDetaching([
        $tenantB->getKey() => ['role' => 'owner'],
    ]);

    $runA = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenantA->getKey(),
        'workspace_id' => (int) $tenantA->workspace_id,
        'type' => 'policy.sync',
        'status' => 'queued',
        'outcome' => 'pending',
        'initiator_name' => 'ManagedEnvironment A Scope',
    ]);

    $runB = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenantB->getKey(),
        'workspace_id' => (int) $tenantB->workspace_id,
        'type' => 'inventory_sync',
        'status' => 'queued',
        'outcome' => 'pending',
        'initiator_name' => 'ManagedEnvironment B Scope',
    ]);

    setAdminPanelContext($tenantA);

    $this->withSession([WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id]);
    session([WorkspaceContext::SESSION_KEY => (int) $tenantA->workspace_id]);

    Livewire::actingAs($user)
        ->test(Operations::class)
        ->assertCanSeeTableRecords([$runA, $runB])
        ->assertSet('tableFilters.managed_environment_id.value', null);
});

test('operation run view is not accessible cross-workspace', function (): void {
    $workspaceA = Workspace::factory()->create();
    $workspaceB = Workspace::factory()->create();

    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspaceA->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'owner',
    ]);

    $tenantB = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $workspaceB->getKey(),
    ]);

    $runB = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenantB->getKey(),
        'workspace_id' => (int) $workspaceB->getKey(),
        'type' => 'inventory_sync',
        'status' => 'queued',
        'outcome' => 'pending',
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspaceA->getKey()])
        ->get(\App\Support\OperationRunLinks::tenantlessView($runB))
        ->assertNotFound();
});

test('readonly users can view operation runs in their workspace', function (): void {
    $tenant = ManagedEnvironment::factory()->create();

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'baseline_compare',
        'status' => 'queued',
        'outcome' => 'pending',
    ]);

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

    Filament::setTenant(null, true);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::index())
        ->assertOk()
        ->assertSee('Baseline compare');

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertOk()
        ->assertSee(\App\Support\OperationRunLinks::identifier($run));
});

test('tenant-associated run viewer requires tenant entitlement even for workspace members', function (): void {
    $workspace = Workspace::factory()->create();
    $tenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'status' => 'active',
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $workspace->getKey(),
        'type' => 'provider.connection.check',
        'status' => 'queued',
        'outcome' => 'pending',
    ]);

    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'owner',
    ]);

    $visibleTenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'status' => 'active',
    ]);

    $user->tenants()->syncWithoutDetaching([
        $visibleTenant->getKey() => ['role' => 'owner'],
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertNotFound();
});

test('baseline compare runs with RBAC context still require tenant entitlement', function (): void {
    $workspace = Workspace::factory()->create();
    $tenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'status' => 'active',
    ]);

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $workspace->getKey(),
        'type' => 'baseline_compare',
        'status' => 'completed',
        'outcome' => 'succeeded',
        'context' => [
            'baseline_profile_id' => 42,
            'baseline_compare' => [
                'rbac_role_definitions' => [
                    'total_compared' => 3,
                    'unchanged' => 1,
                    'modified' => 1,
                    'missing' => 1,
                    'unexpected' => 0,
                ],
            ],
        ],
    ]);

    $user = User::factory()->create();

    WorkspaceMembership::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'user_id' => (int) $user->getKey(),
        'role' => 'owner',
    ]);

    $visibleTenant = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $workspace->getKey(),
        'status' => 'active',
    ]);

    $user->tenants()->syncWithoutDetaching([
        $visibleTenant->getKey() => ['role' => 'owner'],
    ]);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertNotFound();
});

test('canonical run detail returns 403 for in-scope members missing the required capability', function (): void {
    $tenant = ManagedEnvironment::factory()->create();

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'inventory_sync',
        'status' => 'queued',
        'outcome' => 'pending',
    ]);

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

    Filament::setTenant(null, true);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertForbidden();
});

test('reconciled lifecycle run detail keeps capability denial semantics', function (): void {
    $tenant = ManagedEnvironment::factory()->create();

    $run = OperationRun::factory()->create([
        'managed_environment_id' => (int) $tenant->getKey(),
        'workspace_id' => (int) $tenant->workspace_id,
        'type' => 'inventory_sync',
        'status' => 'completed',
        'outcome' => 'failed',
        'context' => [
            'reason_code' => 'run.infrastructure_timeout_or_abandonment',
            'reconciliation' => [
                'reconciled_at' => now('UTC')->toIso8601String(),
                'reason' => 'Infrastructure ended the run before completion.',
                'reason_code' => 'run.infrastructure_timeout_or_abandonment',
                'reason_message' => 'Infrastructure ended the run before completion.',
                'source' => 'failed_callback',
            ],
        ],
        'failure_summary' => [[
            'code' => 'operation.failed',
            'reason_code' => 'run.infrastructure_timeout_or_abandonment',
            'message' => 'Infrastructure ended the run before completion.',
        ]],
    ]);

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

    Filament::setTenant(null, true);

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(\App\Support\OperationRunLinks::tenantlessView($run))
        ->assertForbidden();
});

test('tenant-scoped restore run actions return 404 for forged foreign-tenant run keys', function (): void {
    $tenantA = ManagedEnvironment::factory()->create();
    [$user, $tenantA] = createUserWithTenant($tenantA, role: 'owner');

    $tenantB = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $tenantA->workspace_id,
    ]);

    $user->tenants()->syncWithoutDetaching([
        $tenantB->getKey() => ['role' => 'owner'],
    ]);

    $backupSet = BackupSet::factory()->create([
        'managed_environment_id' => (int) $tenantB->getKey(),
    ]);

    $foreignRun = RestoreRun::factory()->create([
        'managed_environment_id' => (int) $tenantB->getKey(),
        'backup_set_id' => (int) $backupSet->getKey(),
        'status' => 'completed',
        'is_dry_run' => true,
        'requested_by' => 'owner@example.com',
    ]);

    Filament::setTenant($tenantA, true);

    $component = Livewire::actingAs($user)
        ->test(ListRestoreRuns::class);

    expect(fn () => $component->instance()->mountTableAction('archive', (string) $foreignRun->getKey()))
        ->toThrow(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException::class);

    expect($foreignRun->fresh()?->trashed())->toBeFalse();
});
