<?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\OperationRun;
use App\Models\Workspace;
use App\Services\Alerts\AlertDispatchService;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

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

/**
 * @return array{0: AlertRule, 1: AlertDestination}
 */
function createBaselineCompareFailedRuleWithDestination(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_compare_failed',
        '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 invokeBaselineCompareFailedEvents(int $workspaceId, CarbonImmutable $windowStart): array
{
    $job = new EvaluateAlertsJob($workspaceId);
    $reflection = new ReflectionMethod($job, 'baselineCompareFailedEvents');

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

    return $events;
}

it('produces baseline compare failed events for failed and partially succeeded baseline compare runs', 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();

    $failedRun = OperationRun::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => OperationRunType::BaselineCompare->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'completed_at' => $now->subMinutes(10),
        'failure_summary' => [
            ['message' => 'The baseline compare failed.'],
        ],
    ]);

    $partialRun = OperationRun::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => OperationRunType::BaselineCompare->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::PartiallySucceeded->value,
        'completed_at' => $now->subMinutes(5),
        'failure_summary' => [
            ['message' => 'The baseline compare partially succeeded.'],
        ],
    ]);

    OperationRun::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => OperationRunType::BaselineCompare->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Succeeded->value,
        'completed_at' => $now->subMinutes(5),
    ]);

    OperationRun::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => OperationRunType::InventorySync->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'completed_at' => $now->subMinutes(5),
    ]);

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

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

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

    expect($eventsByRunId[$failedRun->getKey()])
        ->toMatchArray([
            'event_type' => 'baseline_compare_failed',
            'managed_environment_id' => (int) $tenant->getKey(),
            'severity' => 'high',
            'fingerprint_key' => 'operation_run:'.$failedRun->getKey(),
            'metadata' => [
                'operation_run_id' => (int) $failedRun->getKey(),
            ],
        ]);

    expect($eventsByRunId[$partialRun->getKey()])
        ->toMatchArray([
            'event_type' => 'baseline_compare_failed',
            'managed_environment_id' => (int) $tenant->getKey(),
            'severity' => 'high',
            'fingerprint_key' => 'operation_run:'.$partialRun->getKey(),
            'metadata' => [
                'operation_run_id' => (int) $partialRun->getKey(),
            ],
        ]);
});

it('keeps baseline compare failed events compatible with dispatcher cooldown dedupe', function (): void {
    [$user, $tenant] = createUserWithTenant(role: 'owner');
    $workspaceId = (int) session()->get(\App\Support\Workspaces\WorkspaceContext::SESSION_KEY);
    [$rule, $destination] = createBaselineCompareFailedRuleWithDestination($workspaceId, cooldownSeconds: 3600);

    $run = OperationRun::factory()->create([
        'workspace_id' => $workspaceId,
        'managed_environment_id' => (int) $tenant->getKey(),
        'type' => OperationRunType::BaselineCompare->value,
        'status' => OperationRunStatus::Completed->value,
        'outcome' => OperationRunOutcome::Failed->value,
        'completed_at' => now(),
    ]);

    $event = [
        'event_type' => 'baseline_compare_failed',
        'managed_environment_id' => (int) $tenant->getKey(),
        'severity' => 'high',
        'fingerprint_key' => 'operation_run:'.$run->getKey(),
        'title' => 'Baseline compare failed',
        'body' => 'The baseline compare failed.',
        'metadata' => [
            'operation_run_id' => (int) $run->getKey(),
        ],
    ];

    $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_compare_failed')
        ->orderBy('id')
        ->get();

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