<?php

declare(strict_types=1);

use App\Filament\Pages\Findings\FindingsHygieneReport;
use App\Models\AuditLog;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Services\Auth\CapabilityResolver;
use App\Support\Audit\AuditActionId;
use App\Support\Auth\Capabilities;
use App\Support\Workspaces\WorkspaceContext;
use Carbon\CarbonImmutable;
use Livewire\Livewire;

use function Pest\Laravel\mock;

afterEach(function (): void {
    CarbonImmutable::setTestNow();
});

function findingsHygieneActingUser(string $role = 'readonly', string $workspaceRole = 'readonly'): array
{
    $tenant = ManagedEnvironment::factory()->create(['status' => 'active']);
    [$user, $tenant] = createUserWithTenant($tenant, role: $role, workspaceRole: $workspaceRole);

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

    return [$user, $tenant];
}

function findingsHygienePage(?User $user = null, array $query = [])
{
    if ($user instanceof User) {
        test()->actingAs($user);
    }

    setAdminPanelContext();

    $factory = $query === []
        ? Livewire::actingAs(auth()->user())
        : Livewire::withQueryParams($query)->actingAs(auth()->user());

    return $factory->test(FindingsHygieneReport::class);
}

function makeFindingsHygieneFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
{
    $subjectDisplayName = $attributes['subject_display_name'] ?? null;
    unset($attributes['subject_display_name']);

    if (is_string($subjectDisplayName) && $subjectDisplayName !== '') {
        $attributes['evidence_jsonb'] = array_merge(
            is_array($attributes['evidence_jsonb'] ?? null) ? $attributes['evidence_jsonb'] : [],
            ['display_name' => $subjectDisplayName],
        );
    }

    return Finding::factory()->for($tenant)->create(array_merge([
        'workspace_id' => (int) $tenant->workspace_id,
        'subject_external_id' => fake()->uuid(),
        'status' => Finding::STATUS_TRIAGED,
    ], $attributes));
}

function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmutable $recordedAt): AuditLog
{
    return AuditLog::query()->create([
        'workspace_id' => (int) $finding->workspace_id,
        'managed_environment_id' => (int) $finding->managed_environment_id,
        'action' => $action,
        'status' => 'success',
        'resource_type' => 'finding',
        'resource_id' => (string) $finding->getKey(),
        'summary' => 'Test workflow activity',
        'recorded_at' => $recordedAt,
    ]);
}

it('redirects hygiene report visits without workspace context into the existing workspace chooser flow', function (): void {
    $user = User::factory()->create();

    $workspaceA = Workspace::factory()->create();
    $workspaceB = Workspace::factory()->create();

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

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

    $this->actingAs($user)
        ->get(FindingsHygieneReport::getUrl(panel: 'admin'))
        ->assertRedirect('/admin/choose-workspace');
});

it('returns 404 for users outside the active workspace on the hygiene report route', function (): void {
    $user = User::factory()->create();
    $workspace = Workspace::factory()->create();

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

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(FindingsHygieneReport::getUrl(panel: 'admin'))
        ->assertNotFound();
});

it('keeps the hygiene report accessible and calm for workspace members with zero visible hygiene scope', function (): void {
    $user = User::factory()->create();
    $workspace = Workspace::factory()->create(['name' => 'Calm Workspace']);

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

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $workspace->getKey()])
        ->get(FindingsHygieneReport::getUrl(panel: 'admin'))
        ->assertOk()
        ->assertSee('Findings hygiene report')
        ->assertSee('No visible hygiene issues right now');
});

it('shows visible hygiene findings with reason labels, last activity, and row drilldown into tenant finding detail', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 22, 9, 0, 0, 'UTC'));

    [$user, $tenant] = findingsHygieneActingUser();

    $lostMember = User::factory()->create(['name' => 'Lost Member']);
    createUserWithTenant($tenant, $lostMember, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('user_id', (int) $lostMember->getKey())
        ->delete();

    $brokenAssignment = makeFindingsHygieneFinding($tenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'status' => Finding::STATUS_TRIAGED,
        'subject_display_name' => 'Broken Assignment Finding',
    ]);

    $staleInProgress = makeFindingsHygieneFinding($tenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $user->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'subject_display_name' => 'Stale Progress Finding',
    ]);
    recordFindingsHygieneAudit($staleInProgress, AuditActionId::FindingInProgress->value, CarbonImmutable::now()->subDays(10));

    findingsHygienePage($user)
        ->assertCanSeeTableRecords([$brokenAssignment, $staleInProgress])
        ->assertSee('Broken assignment')
        ->assertSee('Stale in progress')
        ->assertSee('Lost Member')
        ->assertSee('No current environment access');

    $this->actingAs($user)
        ->withSession([WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id])
        ->get(FindingsHygieneReport::getUrl(panel: 'admin'))
        ->assertOk()
        ->assertSee('Broken Assignment Finding')
        ->assertSee('/findings/'.$brokenAssignment->getKey(), false);
});

it('suppresses hidden-tenant rows, counts, and tenant filter values inside an otherwise available report', function (): void {
    $visibleTenant = ManagedEnvironment::factory()->create(['status' => 'active', 'name' => 'Visible ManagedEnvironment']);
    [$user, $visibleTenant] = createUserWithTenant($visibleTenant, role: 'readonly', workspaceRole: 'readonly');

    $hiddenTenant = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $visibleTenant->workspace_id,
        'name' => 'Hidden ManagedEnvironment',
    ]);
    createUserWithTenant($hiddenTenant, $user, role: 'readonly', workspaceRole: 'readonly');

    $visibleAssignee = User::factory()->create(['name' => 'Visible Assignee']);
    createUserWithTenant($visibleTenant, $visibleAssignee, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $visibleTenant->getKey())
        ->where('user_id', (int) $visibleAssignee->getKey())
        ->delete();

    $hiddenAssignee = User::factory()->create(['name' => 'Hidden Assignee']);
    createUserWithTenant($hiddenTenant, $hiddenAssignee, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $hiddenTenant->getKey())
        ->where('user_id', (int) $hiddenAssignee->getKey())
        ->delete();

    $visibleFinding = makeFindingsHygieneFinding($visibleTenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $visibleAssignee->getKey(),
        'subject_display_name' => 'Visible Hygiene Finding',
    ]);

    $hiddenFinding = makeFindingsHygieneFinding($hiddenTenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $hiddenAssignee->getKey(),
        'subject_display_name' => 'Hidden Hygiene Finding',
    ]);

    mock(CapabilityResolver::class, function ($mock) use ($visibleTenant, $hiddenTenant): void {
        $mock->shouldReceive('primeMemberships')->atLeast()->once();
        $mock->shouldReceive('can')
            ->andReturnUsing(static function (User $user, ManagedEnvironment $tenant, string $capability) use ($visibleTenant, $hiddenTenant): bool {
                expect([(int) $visibleTenant->getKey(), (int) $hiddenTenant->getKey()])
                    ->toContain((int) $tenant->getKey());

                return $capability === Capabilities::TENANT_FINDINGS_VIEW
                    && (int) $tenant->getKey() === (int) $visibleTenant->getKey();
            });
    });

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

    $component = findingsHygienePage($user)
        ->assertCanSeeTableRecords([$visibleFinding])
        ->assertCanNotSeeTableRecords([$hiddenFinding])
        ->assertDontSee('Hidden ManagedEnvironment');

    expect($component->instance()->summaryCounts())
        ->toBe([
            'unique_issue_count' => 1,
            'broken_assignment_count' => 1,
            'stale_in_progress_count' => 0,
        ])
        ->and($component->instance()->availableFilters())
        ->toBe([
            [
                'key' => 'hygiene_scope',
                'label' => 'Findings hygiene only',
                'fixed' => true,
                'options' => [],
            ],
            [
                'key' => 'tenant',
                'label' => 'ManagedEnvironment',
                'fixed' => false,
                'options' => [
                    ['value' => (string) $visibleTenant->getKey(), 'label' => $visibleTenant->name],
                ],
            ],
        ]);
});

it('supports fixed reason filters without duplicating a multi-reason finding in the all-issues view', function (): void {
    CarbonImmutable::setTestNow(CarbonImmutable::create(2026, 4, 22, 9, 0, 0, 'UTC'));

    [$user, $tenant] = findingsHygieneActingUser();

    $lostMember = User::factory()->create(['name' => 'Lost Worker']);
    createUserWithTenant($tenant, $lostMember, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('user_id', (int) $lostMember->getKey())
        ->delete();

    $brokenOnly = makeFindingsHygieneFinding($tenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'status' => Finding::STATUS_TRIAGED,
        'subject_display_name' => 'Broken Only',
    ]);

    $staleOnly = makeFindingsHygieneFinding($tenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $user->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(9),
        'subject_display_name' => 'Stale Only',
    ]);
    recordFindingsHygieneAudit($staleOnly, AuditActionId::FindingInProgress->value, CarbonImmutable::now()->subDays(9));

    $brokenAndStale = makeFindingsHygieneFinding($tenant, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'status' => Finding::STATUS_IN_PROGRESS,
        'in_progress_at' => now()->subDays(10),
        'subject_display_name' => 'Broken And Stale',
    ]);
    recordFindingsHygieneAudit($brokenAndStale, AuditActionId::FindingInProgress->value, CarbonImmutable::now()->subDays(10));

    $allIssues = findingsHygienePage($user);
    $brokenAssignments = findingsHygienePage($user, ['reason' => 'broken_assignment']);
    $staleInProgress = findingsHygienePage($user, ['reason' => 'stale_in_progress']);

    $allIssues
        ->assertCanSeeTableRecords([$brokenOnly, $staleOnly, $brokenAndStale])
        ->assertSee('Broken And Stale');

    $brokenAssignments
        ->assertCanSeeTableRecords([$brokenOnly, $brokenAndStale])
        ->assertCanNotSeeTableRecords([$staleOnly]);

    $staleInProgress
        ->assertCanSeeTableRecords([$staleOnly, $brokenAndStale])
        ->assertCanNotSeeTableRecords([$brokenOnly]);

    expect($allIssues->instance()->summaryCounts())
        ->toBe([
            'unique_issue_count' => 3,
            'broken_assignment_count' => 2,
            'stale_in_progress_count' => 2,
        ])
        ->and($allIssues->instance()->availableReasonFilters())
        ->toBe([
            [
                'key' => 'all',
                'label' => 'All issues',
                'active' => true,
                'badge_count' => 3,
                'url' => FindingsHygieneReport::getUrl(panel: 'admin'),
            ],
            [
                'key' => 'broken_assignment',
                'label' => 'Broken assignment',
                'active' => false,
                'badge_count' => 2,
                'url' => FindingsHygieneReport::getUrl(panel: 'admin', parameters: ['reason' => 'broken_assignment']),
            ],
            [
                'key' => 'stale_in_progress',
                'label' => 'Stale in progress',
                'active' => false,
                'badge_count' => 2,
                'url' => FindingsHygieneReport::getUrl(panel: 'admin', parameters: ['reason' => 'stale_in_progress']),
            ],
        ]);
});

it('explains when the active tenant prefilter hides otherwise visible hygiene issues and clears it in place', function (): void {
    [$user, $tenantA] = findingsHygieneActingUser();

    $tenantB = ManagedEnvironment::factory()->create([
        'status' => 'active',
        'workspace_id' => (int) $tenantA->workspace_id,
        'name' => 'Beta ManagedEnvironment',
    ]);
    createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');

    $lostMember = User::factory()->create(['name' => 'Lost Member']);
    createUserWithTenant($tenantA, $lostMember, role: 'readonly', workspaceRole: 'readonly');
    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenantA->getKey())
        ->where('user_id', (int) $lostMember->getKey())
        ->delete();

    $tenantAIssue = makeFindingsHygieneFinding($tenantA, [
        'owner_user_id' => (int) $user->getKey(),
        'assignee_user_id' => (int) $lostMember->getKey(),
        'subject_display_name' => 'ManagedEnvironment A Issue',
    ]);

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

    $component = findingsHygienePage($user)
        ->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
        ->assertCanNotSeeTableRecords([$tenantAIssue])
        ->assertSee('No hygiene issues match this environment scope')
        ->assertActionVisible('clear_tenant_filter');

    $component->callAction('clear_tenant_filter')
        ->assertCanSeeTableRecords([$tenantAIssue]);

    expect($component->instance()->appliedScope())
        ->toBe([
            'workspace_scoped' => true,
            'fixed_scope' => 'visible_findings_hygiene_only',
            'reason_filter' => 'all',
            'reason_filter_label' => 'All issues',
            'tenant_prefilter_source' => 'none',
            'tenant_label' => null,
        ]);
});
