<?php

declare(strict_types=1);

use App\Filament\Resources\FindingResource;
use App\Filament\Resources\FindingResource\Pages\ListFindings;
use App\Filament\Resources\FindingResource\Pages\ViewFinding;
use App\Models\Finding;
use App\Models\FindingException;
use App\Models\ManagedEnvironment;
use App\Models\User;
use Filament\Facades\Filament;
use Filament\Forms\Components\Field;
use Filament\Schemas\Components\Text;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

it('renders explicit responsibility states on findings list and detail surfaces', function (): void {
    [$viewer, $tenant] = createUserWithTenant(role: 'readonly');

    $ownerOnly = tenantFindingUser($tenant, 'Owner Only');
    $listOwner = tenantFindingUser($tenant, 'List Owner');
    $listAssignee = tenantFindingUser($tenant, 'List Assignee');
    $assigneeOnly = tenantFindingUser($tenant, 'Assignee Only');
    $samePerson = tenantFindingUser($tenant, 'Same Person');

    $ownerOnlyFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_NEW,
        'owner_user_id' => (int) $ownerOnly->getKey(),
        'assignee_user_id' => null,
    ]);

    $assignedFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_TRIAGED,
        'owner_user_id' => (int) $listOwner->getKey(),
        'assignee_user_id' => (int) $listAssignee->getKey(),
    ]);

    $assigneeOnlyFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => null,
        'assignee_user_id' => (int) $assigneeOnly->getKey(),
    ]);

    $bothNullFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_REOPENED,
        'owner_user_id' => null,
        'assignee_user_id' => null,
    ]);

    $sameUserFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_NEW,
        'owner_user_id' => (int) $samePerson->getKey(),
        'assignee_user_id' => (int) $samePerson->getKey(),
    ]);

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

    Livewire::test(ListFindings::class)
        ->assertCanSeeTableRecords([
            $ownerOnlyFinding,
            $assignedFinding,
            $assigneeOnlyFinding,
            $bothNullFinding,
            $sameUserFinding,
        ])
        ->assertSee('Responsibility')
        ->assertSee('Accountable owner')
        ->assertSee('Active assignee')
        ->assertSee('owned but unassigned')
        ->assertSee('assigned')
        ->assertSee('orphaned accountability')
        ->assertSee('Owner Only')
        ->assertSee('List Owner')
        ->assertSee('List Assignee')
        ->assertSee('Assignee Only')
        ->assertSee('Same Person');

    Livewire::test(ViewFinding::class, ['record' => $assigneeOnlyFinding->getKey()])
        ->assertSee('Responsibility state')
        ->assertSee('orphaned accountability')
        ->assertSee('Accountable owner')
        ->assertSee('Active assignee')
        ->assertSee('Assignee Only');

    Livewire::test(ViewFinding::class, ['record' => $sameUserFinding->getKey()])
        ->assertSee('assigned')
        ->assertSee('Same Person');
});

it('isolates owner accountability and assigned work with separate personal filters', function (): void {
    [$viewer, $tenant] = createUserWithTenant(role: 'manager');

    $otherOwner = tenantFindingUser($tenant, 'Other Owner');
    $otherAssignee = tenantFindingUser($tenant, 'Other Assignee');

    $assignedToMe = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_NEW,
        'owner_user_id' => (int) $otherOwner->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
    ]);

    $ownedByMe = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_TRIAGED,
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $otherAssignee->getKey(),
    ]);

    $bothMine = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_REOPENED,
        'owner_user_id' => (int) $viewer->getKey(),
        'assignee_user_id' => (int) $viewer->getKey(),
    ]);

    $neitherMine = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => (int) $otherOwner->getKey(),
        'assignee_user_id' => (int) $otherAssignee->getKey(),
    ]);

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

    Livewire::test(ListFindings::class)
        ->filterTable('my_assigned', true)
        ->assertCanSeeTableRecords([$assignedToMe, $bothMine])
        ->assertCanNotSeeTableRecords([$ownedByMe, $neitherMine])
        ->removeTableFilter('my_assigned')
        ->filterTable('my_accountability', true)
        ->assertCanSeeTableRecords([$ownedByMe, $bothMine])
        ->assertCanNotSeeTableRecords([$assignedToMe, $neitherMine]);
});

it('keeps exception ownership visibly separate from finding ownership', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');

    $findingOwner = tenantFindingUser($tenant, 'Finding Owner');
    $findingAssignee = tenantFindingUser($tenant, 'Finding Assignee');
    $exceptionOwner = tenantFindingUser($tenant, 'Exception Owner');

    $findingWithException = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_NEW,
        'owner_user_id' => (int) $findingOwner->getKey(),
        'assignee_user_id' => (int) $findingAssignee->getKey(),
    ]);

    FindingException::query()->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'managed_environment_id' => (int) $tenant->getKey(),
        'finding_id' => (int) $findingWithException->getKey(),
        'requested_by_user_id' => (int) $owner->getKey(),
        'owner_user_id' => (int) $exceptionOwner->getKey(),
        'status' => FindingException::STATUS_PENDING,
        'current_validity_state' => FindingException::VALIDITY_MISSING_SUPPORT,
        'request_reason' => 'Needs temporary governance coverage.',
        'requested_at' => now(),
        'review_due_at' => now()->addDays(7),
        'evidence_summary' => ['reference_count' => 0],
    ]);

    $requestFinding = Finding::factory()->for($tenant)->create([
        'status' => Finding::STATUS_NEW,
        'owner_user_id' => (int) $findingOwner->getKey(),
    ]);

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

    $this->get(FindingResource::getUrl('view', ['record' => $findingWithException], panel: 'admin', tenant: $tenant))
        ->assertSuccessful()
        ->assertSee('Accountable owner')
        ->assertSee('Active assignee')
        ->assertSee('Exception owner')
        ->assertSee('Finding Owner')
        ->assertSee('Finding Assignee')
        ->assertSee('Exception Owner');

    $component = Livewire::test(ViewFinding::class, ['record' => $requestFinding->getKey()])
        ->mountAction('request_exception');

    $method = new \ReflectionMethod($component->instance(), 'getMountedActionForm');
    $method->setAccessible(true);

    $form = $method->invoke($component->instance());

    $field = collect($form?->getFlatFields(withHidden: true) ?? [])
        ->first(fn (Field $field): bool => $field->getName() === 'owner_user_id');

    $helperText = collect($field?->getChildSchema(Field::BELOW_CONTENT_SCHEMA_KEY)?->getComponents() ?? [])
        ->filter(fn (mixed $schemaComponent): bool => $schemaComponent instanceof Text)
        ->map(fn (Text $schemaComponent): string => (string) $schemaComponent->getContent())
        ->implode(' ');

    expect($field?->getLabel())->toBe('Exception owner')
        ->and($helperText)->toContain('Owns the exception record')
        ->and($helperText)->toContain('not the finding outcome');
});

it('allows in-scope members and returns 404 for non-members on tenant findings routes', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [$member, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');

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

    $this->actingAs($member)
        ->get(FindingResource::getUrl('index', panel: 'admin', tenant: $tenant))
        ->assertSuccessful();

    $this->actingAs($member)
        ->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
        ->assertSuccessful();

    $tenantInSameWorkspace = ManagedEnvironment::factory()->create([
        'workspace_id' => (int) $tenant->workspace_id,
    ]);
    [$outsider] = createUserWithTenant(tenant: $tenantInSameWorkspace, role: 'owner');

    $this->actingAs($outsider)
        ->get(FindingResource::getUrl('index', panel: 'admin', tenant: $tenant))
        ->assertNotFound();

    $this->actingAs($outsider)
        ->get(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
        ->assertNotFound();
});

function tenantFindingUser(ManagedEnvironment $tenant, string $name): User
{
    $user = User::factory()->create([
        'name' => $name,
    ]);

    createUserWithTenant(tenant: $tenant, user: $user, role: 'operator');

    return $user;
}
