<?php

declare(strict_types=1);

use App\Jobs\Alerts\EvaluateAlertsJob;
use App\Models\AlertDelivery;
use App\Models\AlertDestination;
use App\Models\AlertRule;
use App\Models\Finding;
use App\Models\Workspace;
use App\Models\WorkspaceSetting;
use App\Services\Alerts\AlertDispatchService;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

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

/**
 * @return array{0: AlertRule, 1: AlertDestination}
 */
function createBaselineHighDriftRuleWithDestination(int $workspaceId, int $cooldownSeconds = 0): array
{
    $destination = AlertDestination::factory()->create([
        'workspace_id' => $workspaceId,
        'is_enabled' => true,
    ]);

    $rule = AlertRule::factory()->create([
        'workspace_id' => $workspaceId,
        'event_type' => 'baseline_high_drift',
        'minimum_severity' => 'low',
        'is_enabled' => true,
        'cooldown_seconds' => $cooldownSeconds,
    ]);

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

    return [$rule, $destination];
}

/**
 * @return array<int, array<string, mixed>>
 */
function invokeBaselineHighDriftEvents(int $workspaceId, CarbonImmutable $windowStart): array
{
    $job = new EvaluateAlertsJob($workspaceId);
    $reflection = new ReflectionMethod($job, 'baselineHighDriftEvents');

    /** @var array<int, array<string, mixed>> $events */
    $events = $reflection->invoke($job, $workspaceId, $windowStart);

    return $events;
}

it('produces baseline drift events only for new and reopened baseline findings that meet the workspace threshold', function (): void {
    $now = CarbonImmutable::parse('2026-02-28T12:00:00Z');
    CarbonImmutable::setTestNow($now);

    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY);
    $windowStart = $now->subHour();

    WorkspaceSetting::query()->create([
        'workspace_id' => $workspaceId,
        'domain' => 'baseline',
        'key' => 'alert_min_severity',
        'value' => Finding::SEVERITY_HIGH,
        'updated_by_user_id' => (int) $user->getKey(),
    ]);

    $newFinding = Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'baseline.compare',
        'fingerprint' => 'baseline-fingerprint-new',
        'severity' => Finding::SEVERITY_CRITICAL,
        'status' => Finding::STATUS_NEW,
        'created_at' => $now->subMinutes(10),
        'evidence_jsonb' => ['change_type' => 'missing_policy'],
    ]);

    $reopenedFinding = Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'baseline.compare',
        'fingerprint' => 'baseline-fingerprint-reopened',
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_REOPENED,
        'reopened_at' => $now->subMinutes(5),
        'evidence_jsonb' => ['change_type' => 'different_version'],
    ]);

    Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'baseline.compare',
        'fingerprint' => 'baseline-too-old',
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
        'created_at' => $now->subDays(1),
        'evidence_jsonb' => ['change_type' => 'missing_policy'],
    ]);

    Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'baseline.compare',
        'fingerprint' => 'baseline-below-threshold',
        'severity' => Finding::SEVERITY_MEDIUM,
        'status' => Finding::STATUS_NEW,
        'created_at' => $now->subMinutes(5),
        'evidence_jsonb' => ['change_type' => 'unexpected_policy'],
    ]);

    Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'permission_check',
        'fingerprint' => 'not-baseline',
        'severity' => Finding::SEVERITY_CRITICAL,
        'status' => Finding::STATUS_NEW,
        'created_at' => $now->subMinutes(5),
        'evidence_jsonb' => ['change_type' => 'missing_policy'],
    ]);

    $events = invokeBaselineHighDriftEvents($workspaceId, $windowStart);

    expect($events)->toHaveCount(2);

    $eventsByFindingId = collect($events)->keyBy(static fn (array $event): int => (int) $event['metadata']['finding_id']);

    expect($eventsByFindingId[$newFinding->getKey()])
        ->toMatchArray([
            'event_type' => 'baseline_high_drift',
            'managed_environment_id' => (int) $tenant->getKey(),
            'severity' => Finding::SEVERITY_CRITICAL,
            'fingerprint_key' => 'finding_fingerprint:baseline-fingerprint-new',
            'metadata' => [
                'finding_id' => (int) $newFinding->getKey(),
                'finding_fingerprint' => 'baseline-fingerprint-new',
                'change_type' => 'missing_policy',
            ],
        ]);

    expect($eventsByFindingId[$reopenedFinding->getKey()])
        ->toMatchArray([
            'event_type' => 'baseline_high_drift',
            'managed_environment_id' => (int) $tenant->getKey(),
            'severity' => Finding::SEVERITY_HIGH,
            'fingerprint_key' => 'finding_fingerprint:baseline-fingerprint-reopened',
            'metadata' => [
                'finding_id' => (int) $reopenedFinding->getKey(),
                'finding_fingerprint' => 'baseline-fingerprint-reopened',
                'change_type' => 'different_version',
            ],
        ]);
});

it('uses the finding fingerprint for dedupe and remains cooldown compatible', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY);
    [$rule, $destination] = createBaselineHighDriftRuleWithDestination($workspaceId, cooldownSeconds: 3600);

    $finding = Finding::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'source' => 'baseline.compare',
        'fingerprint' => 'stable-fingerprint-key',
        'severity' => Finding::SEVERITY_HIGH,
        'status' => Finding::STATUS_NEW,
        'evidence_jsonb' => ['change_type' => 'missing_policy'],
    ]);

    $event = [
        'event_type' => 'baseline_high_drift',
        'managed_environment_id' => (int) $tenant->getKey(),
        'severity' => Finding::SEVERITY_HIGH,
        'fingerprint_key' => 'finding_fingerprint:stable-fingerprint-key',
        'title' => 'Baseline drift detected',
        'body' => 'A baseline finding was created.',
        'metadata' => [
            'finding_id' => (int) $finding->getKey(),
            'finding_fingerprint' => 'stable-fingerprint-key',
            'change_type' => 'missing_policy',
        ],
    ];

    $workspace = Workspace::query()->findOrFail($workspaceId);
    $dispatchService = app(AlertDispatchService::class);

    expect($dispatchService->dispatchEvent($workspace, $event))->toBe(1);
    expect($dispatchService->dispatchEvent($workspace, $event))->toBe(1);

    $deliveries = AlertDelivery::query()
        ->where('workspace_id', $workspaceId)
        ->where('alert_rule_id', (int) $rule->getKey())
        ->where('alert_destination_id', (int) $destination->getKey())
        ->where('event_type', 'baseline_high_drift')
        ->orderBy('id')
        ->get();

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