<?php

declare(strict_types=1);

use App\Filament\Resources\FindingResource;
use App\Models\AlertRule;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentMembership;
use App\Models\User;
use App\Notifications\Findings\FindingEventNotification;
use App\Services\Auth\CapabilityResolver;
use App\Services\Findings\FindingNotificationService;
use App\Support\Auth\Capabilities;
use Filament\Facades\Filament;
use Filament\Notifications\Notification as FilamentNotification;
use Illuminate\Support\Facades\Gate;

if (! function_exists('spec230ExpectedNotificationIcon')) {
    function spec230ExpectedNotificationIcon(string $status): string
    {
        return (string) data_get(
            FilamentNotification::make()->status($status)->getDatabaseMessage(),
            'icon',
            '',
        );
    }
}

it('stores a filament payload with one tenant finding deep link and recipient reason copy', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $assignee = User::factory()->create(['name' => 'Assigned Operator']);
    createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');

    $finding = Finding::factory()->for($tenant)->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => Finding::STATUS_TRIAGED,
        'owner_user_id' => (int) $owner->getKey(),
        'assignee_user_id' => (int) $assignee->getKey(),
    ]);

    app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);

    $notification = $assignee->notifications()
        ->where('type', FindingEventNotification::class)
        ->latest('id')
        ->first();

    expect($notification)->not->toBeNull()
        ->and(data_get($notification?->data, 'format'))->toBe('filament')
        ->and(data_get($notification?->data, 'title'))->toBe('Finding assigned')
        ->and(data_get($notification?->data, 'status'))->toBe('info')
        ->and(data_get($notification?->data, 'icon'))->toBe(spec230ExpectedNotificationIcon('info'))
        ->and(data_get($notification?->data, 'actions.0.label'))->toBe('Open finding')
        ->and(data_get($notification?->data, 'actions.0.url'))
        ->toBe(FindingResource::getUrl('view', ['record' => $finding], panel: 'admin', tenant: $tenant))
        ->and(data_get($notification?->data, 'actions.0.target'))->toBe('finding_detail')
        ->and(data_get($notification?->data, 'supporting_lines'))->toBe(['You are the new assignee.'])
        ->and(data_get($notification?->data, 'finding_event.event_type'))->toBe(AlertRule::EVENT_FINDINGS_ASSIGNED)
        ->and(data_get($notification?->data, 'finding_event.recipient_reason'))->toBe('new_assignee');

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

    $this->get(data_get($notification?->data, 'actions.0.url'))->assertSuccessful();
});

it('maps due soon and overdue finding notifications onto the shared status and icon treatment', function (
    string $eventType,
    string $recipient,
    string $expectedStatus,
    string $findingStatus,
    string $relativeDueAt,
): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $assignee = User::factory()->create(['name' => 'Urgency Operator']);
    createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');

    $finding = Finding::factory()->for($tenant)->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => $findingStatus,
        'owner_user_id' => (int) $owner->getKey(),
        'assignee_user_id' => (int) $assignee->getKey(),
        'due_at' => now()->modify($relativeDueAt),
    ]);

    app(FindingNotificationService::class)->dispatch($finding, $eventType);

    $notifiable = $recipient === 'owner' ? $owner : $assignee;
    $notification = $notifiable->notifications()
        ->where('type', FindingEventNotification::class)
        ->latest('id')
        ->first();

    expect($notification)->not->toBeNull()
        ->and(data_get($notification?->data, 'status'))->toBe($expectedStatus)
        ->and(data_get($notification?->data, 'icon'))->toBe(spec230ExpectedNotificationIcon($expectedStatus))
        ->and(data_get($notification?->data, 'actions.0.target'))->toBe('finding_detail');
})->with([
    'due soon' => [AlertRule::EVENT_FINDINGS_DUE_SOON, 'assignee', 'warning', Finding::STATUS_TRIAGED, '+6 hours'],
    'overdue' => [AlertRule::EVENT_FINDINGS_OVERDUE, 'owner', 'danger', Finding::STATUS_IN_PROGRESS, '-2 hours'],
]);

it('returns 404 when a finding notification link is opened after tenant access is removed', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $assignee = User::factory()->create(['name' => 'Removed Operator']);
    createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');

    $finding = Finding::factory()->for($tenant)->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => Finding::STATUS_TRIAGED,
        'owner_user_id' => (int) $owner->getKey(),
        'assignee_user_id' => (int) $assignee->getKey(),
    ]);

    app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);

    $url = data_get($assignee->notifications()->latest('id')->first(), 'data.actions.0.url');

    ManagedEnvironmentMembership::query()
        ->where('managed_environment_id', (int) $tenant->getKey())
        ->where('user_id', (int) $assignee->getKey())
        ->delete();
    createUserWithTenant(
        tenant: ManagedEnvironment::factory()->create(['workspace_id' => (int) $tenant->workspace_id]),
        user: $assignee,
        role: 'operator',
    );

    app(CapabilityResolver::class)->clearCache();

    $this->actingAs($assignee)
        ->get($url)
        ->assertNotFound();
});

it('returns 403 when a finding notification link is opened by an in-scope member without findings view capability', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $assignee = User::factory()->create(['name' => 'Scoped Operator']);
    createUserWithTenant(tenant: $tenant, user: $assignee, role: 'operator');

    $finding = Finding::factory()->for($tenant)->create([
        'workspace_id' => (int) $tenant->workspace_id,
        'status' => Finding::STATUS_TRIAGED,
        'owner_user_id' => (int) $owner->getKey(),
        'assignee_user_id' => (int) $assignee->getKey(),
    ]);

    app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);

    $url = data_get($assignee->notifications()->latest('id')->first(), 'data.actions.0.url');

    Gate::define(Capabilities::TENANT_FINDINGS_VIEW, fn (): bool => false);

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

    $this->get($url)->assertForbidden();
});
