<?php

declare(strict_types=1);

use App\Filament\Resources\AlertDeliveryResource;
use App\Filament\Resources\AlertDeliveryResource\Pages\ListAlertDeliveries;
use App\Filament\Resources\AlertRuleResource;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Models\Finding;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Models\WorkspaceMembership;
use App\Notifications\Findings\FindingEventNotification;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\Findings\FindingNotificationService;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Facades\Filament;
use Livewire\Livewire;

it('exposes the four finding notification events in the existing alert rule options', function (): void {
    expect(AlertRuleResource::eventTypeOptions())->toMatchArray([
        AlertRule::EVENT_FINDINGS_ASSIGNED => 'Finding assigned',
        AlertRule::EVENT_FINDINGS_REOPENED => 'Finding reopened',
        AlertRule::EVENT_FINDINGS_DUE_SOON => 'Finding due soon',
        AlertRule::EVENT_FINDINGS_OVERDUE => 'Finding overdue',
    ]);
});

it('delivers a direct finding notification without requiring a matching alert rule', 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(),
    ]);

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

    expect($result['direct_delivery_status'])->toBe('sent')
        ->and($result['external_delivery_count'])->toBe(0)
        ->and($assignee->notifications()->where('type', FindingEventNotification::class)->count())->toBe(1)
        ->and(AlertDelivery::query()->count())->toBe(0);
});

it('fans out matching external copies through the existing alert delivery pipeline', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) $tenant->workspace_id;

    $destination = AlertDestination::factory()->create([
        'workspace_id' => $workspaceId,
        'is_enabled' => true,
    ]);

    $rule = AlertRule::factory()->create([
        'workspace_id' => $workspaceId,
        'event_type' => AlertRule::EVENT_FINDINGS_OVERDUE,
        'minimum_severity' => Finding::SEVERITY_MEDIUM,
        'is_enabled' => true,
        'cooldown_seconds' => 0,
    ]);

    $rule->destinations()->attach($destination->getKey(), [
        'workspace_id' => $workspaceId,
    ]);

    $finding = Finding::factory()->for($tenant)->create([
        'workspace_id' => $workspaceId,
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => (int) $owner->getKey(),
        'assignee_user_id' => null,
        'severity' => Finding::SEVERITY_HIGH,
        'due_at' => now()->subHour(),
    ]);

    $result = app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_OVERDUE);

    $delivery = AlertDelivery::query()->latest('id')->first();

    expect($result['direct_delivery_status'])->toBe('sent')
        ->and($result['external_delivery_count'])->toBe(1)
        ->and($delivery)->not->toBeNull()
        ->and($delivery?->event_type)->toBe(AlertRule::EVENT_FINDINGS_OVERDUE)
        ->and(data_get($delivery?->payload, 'title'))->toBe('Finding overdue');
});

it('inherits minimum severity tenant scoping and cooldown suppression for finding alert copies', function (): void {
    [$ownerA, $tenantA] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) $tenantA->workspace_id;

    $tenantB = ManagedEnvironment::factory()->create([
        'workspace_id' => $workspaceId,
    ]);
    [$ownerB] = createUserWithTenant(tenant: $tenantB, role: 'owner');

    $destination = AlertDestination::factory()->create([
        'workspace_id' => $workspaceId,
        'is_enabled' => true,
    ]);

    $rule = AlertRule::factory()->create([
        'workspace_id' => $workspaceId,
        'event_type' => AlertRule::EVENT_FINDINGS_OVERDUE,
        'minimum_severity' => Finding::SEVERITY_HIGH,
        'tenant_scope_mode' => AlertRule::TENANT_SCOPE_ALLOWLIST,
        'tenant_allowlist' => [(int) $tenantA->getKey()],
        'is_enabled' => true,
        'cooldown_seconds' => 3600,
    ]);

    $rule->destinations()->attach($destination->getKey(), [
        'workspace_id' => $workspaceId,
    ]);

    $mediumFinding = Finding::factory()->for($tenantA)->create([
        'workspace_id' => $workspaceId,
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => (int) $ownerA->getKey(),
        'severity' => Finding::SEVERITY_MEDIUM,
        'due_at' => now()->subHour(),
    ]);

    $scopedOutFinding = Finding::factory()->for($tenantB)->create([
        'workspace_id' => $workspaceId,
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => (int) $ownerB->getKey(),
        'severity' => Finding::SEVERITY_CRITICAL,
        'due_at' => now()->subHour(),
    ]);

    $trackedFinding = Finding::factory()->for($tenantA)->create([
        'workspace_id' => $workspaceId,
        'status' => Finding::STATUS_IN_PROGRESS,
        'owner_user_id' => (int) $ownerA->getKey(),
        'severity' => Finding::SEVERITY_HIGH,
        'due_at' => now()->subHour(),
    ]);

    expect(app(FindingNotificationService::class)->dispatch($mediumFinding, AlertRule::EVENT_FINDINGS_OVERDUE)['external_delivery_count'])->toBe(0)
        ->and(app(FindingNotificationService::class)->dispatch($scopedOutFinding, AlertRule::EVENT_FINDINGS_OVERDUE)['external_delivery_count'])->toBe(0);

    app(FindingNotificationService::class)->dispatch($trackedFinding, AlertRule::EVENT_FINDINGS_OVERDUE);
    app(FindingNotificationService::class)->dispatch($trackedFinding->fresh(), AlertRule::EVENT_FINDINGS_OVERDUE);

    $deliveries = AlertDelivery::query()
        ->where('workspace_id', $workspaceId)
        ->where('event_type', AlertRule::EVENT_FINDINGS_OVERDUE)
        ->orderBy('id')
        ->get();

    expect($deliveries)->toHaveCount(2)
        ->and($deliveries[0]->status)->toBe(AlertDelivery::STATUS_QUEUED)
        ->and($deliveries[1]->status)->toBe(AlertDelivery::STATUS_SUPPRESSED);
});

it('inherits quiet hours deferral for finding alert copies', function (): void {
    [$owner, $tenant] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) $tenant->workspace_id;

    $destination = AlertDestination::factory()->create([
        'workspace_id' => $workspaceId,
        'is_enabled' => true,
    ]);

    $rule = AlertRule::factory()->create([
        'workspace_id' => $workspaceId,
        'event_type' => AlertRule::EVENT_FINDINGS_ASSIGNED,
        'minimum_severity' => Finding::SEVERITY_LOW,
        'is_enabled' => true,
        'cooldown_seconds' => 0,
        'quiet_hours_enabled' => true,
        'quiet_hours_start' => '00:00',
        'quiet_hours_end' => '23:59',
        'quiet_hours_timezone' => 'UTC',
    ]);

    $rule->destinations()->attach($destination->getKey(), [
        'workspace_id' => $workspaceId,
    ]);

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

    $result = app(FindingNotificationService::class)->dispatch($finding, AlertRule::EVENT_FINDINGS_ASSIGNED);
    $delivery = AlertDelivery::query()->latest('id')->first();

    expect($result['external_delivery_count'])->toBe(1)
        ->and($delivery)->not->toBeNull()
        ->and($delivery?->status)->toBe(AlertDelivery::STATUS_DEFERRED);
});

it('renders finding event labels and filters in the existing alert deliveries viewer', function (): void {
    $tenant = ManagedEnvironment::factory()->create();
    [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
    $workspaceId = (int) $tenant->workspace_id;

    $rule = AlertRule::factory()->create([
        'workspace_id' => $workspaceId,
        'event_type' => AlertRule::EVENT_FINDINGS_OVERDUE,
    ]);

    $destination = AlertDestination::factory()->create([
        'workspace_id' => $workspaceId,
        'is_enabled' => true,
    ]);

    $delivery = AlertDelivery::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'alert_rule_id' => (int) $rule->getKey(),
        'alert_destination_id' => (int) $destination->getKey(),
        'event_type' => AlertRule::EVENT_FINDINGS_OVERDUE,
        'payload' => [
            'title' => 'Finding overdue',
            'body' => 'A finding is overdue and needs follow-up.',
        ],
    ]);

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

    Livewire::test(ListAlertDeliveries::class)
        ->filterTable('event_type', AlertRule::EVENT_FINDINGS_OVERDUE)
        ->assertCanSeeTableRecords([$delivery])
        ->assertSee('Finding overdue');

    expect(AlertRuleResource::eventTypeLabel(AlertRule::EVENT_FINDINGS_OVERDUE))->toBe('Finding overdue');
});

it('preserves alerts read and mutation boundaries for the existing admin surfaces', function (): void {
    $workspace = Workspace::factory()->create();
    $viewer = User::factory()->create();

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

    session()->put(WorkspaceContext::SESSION_KEY, (int) $workspace->getKey());

    $this->actingAs($viewer)
        ->get(AlertRuleResource::getUrl(panel: 'admin'))
        ->assertOk();

    $this->actingAs($viewer)
        ->get(AlertDeliveryResource::getUrl(panel: 'admin'))
        ->assertOk();

    $this->actingAs($viewer)
        ->get(AlertRuleResource::getUrl('create', panel: 'admin'))
        ->assertForbidden();

    $resolver = \Mockery::mock(WorkspaceCapabilityResolver::class);
    $resolver->shouldReceive('isMember')->andReturnTrue();
    $resolver->shouldReceive('can')->andReturnFalse();
    app()->instance(WorkspaceCapabilityResolver::class, $resolver);

    $this->actingAs($viewer)
        ->get(AlertRuleResource::getUrl(panel: 'admin'))
        ->assertForbidden();

    $this->actingAs($viewer)
        ->get(AlertDeliveryResource::getUrl(panel: 'admin'))
        ->assertForbidden();

    $outsider = User::factory()->create();
    app()->forgetInstance(WorkspaceCapabilityResolver::class);

    $this->actingAs($outsider)
        ->get(AlertRuleResource::getUrl(panel: 'admin'))
        ->assertNotFound();
});
