<?php

declare(strict_types=1);

namespace App\Services\Alerts;

use App\Models\AlertDelivery;
use App\Models\AlertRule;
use App\Models\ManagedEnvironment;
use App\Models\Workspace;
use Carbon\CarbonImmutable;
use Illuminate\Support\Arr;

class AlertDispatchService
{
    public function __construct(
        private readonly AlertFingerprintService $fingerprintService,
        private readonly AlertQuietHoursService $quietHoursService,
    ) {}

    /**
     * @param  array<string, mixed>  $event
     */
    public function dispatchEvent(Workspace $workspace, array $event): int
    {
        $workspaceId = (int) $workspace->getKey();
        $tenantId = (int) ($event['managed_environment_id'] ?? 0);
        $eventType = trim((string) ($event['event_type'] ?? ''));

        if ($workspaceId <= 0 || $tenantId <= 0 || $eventType === '') {
            return 0;
        }

        $tenant = ManagedEnvironment::query()
            ->whereKey($tenantId)
            ->where('workspace_id', $workspaceId)
            ->first();

        if (! $tenant instanceof ManagedEnvironment) {
            return 0;
        }

        $now = CarbonImmutable::now('UTC');
        $eventSeverity = $this->normalizeSeverity((string) ($event['severity'] ?? ''));

        $rules = AlertRule::query()
            ->with(['destinations' => fn ($query) => $query->where('is_enabled', true)])
            ->where('workspace_id', $workspaceId)
            ->where('is_enabled', true)
            ->where('event_type', $eventType)
            ->orderBy('id')
            ->get();

        $createdDeliveries = 0;

        foreach ($rules as $rule) {
            if (! $rule->appliesToTenant($tenantId)) {
                continue;
            }

            if (! $this->meetsMinimumSeverity($eventSeverity, (string) $rule->minimum_severity)) {
                continue;
            }

            foreach ($rule->destinations as $destination) {
                $fingerprintHash = $this->fingerprintService->hash($rule, $destination, $tenantId, $event);

                $isSuppressed = $this->shouldSuppress(
                    workspaceId: $workspaceId,
                    ruleId: (int) $rule->getKey(),
                    destinationId: (int) $destination->getKey(),
                    fingerprintHash: $fingerprintHash,
                    cooldownSeconds: (int) ($rule->cooldown_seconds ?? 0),
                    now: $now,
                );

                $sendAfter = null;
                $status = AlertDelivery::STATUS_QUEUED;

                if ($isSuppressed) {
                    $status = AlertDelivery::STATUS_SUPPRESSED;
                } else {
                    $deferUntil = $this->quietHoursService->deferUntil($rule, $workspace, $now);

                    if ($deferUntil instanceof CarbonImmutable) {
                        $status = AlertDelivery::STATUS_DEFERRED;
                        $sendAfter = $deferUntil;
                    }
                }

                AlertDelivery::query()->create([
                    'workspace_id' => $workspaceId,
                    'managed_environment_id' => $tenantId,
                    'alert_rule_id' => (int) $rule->getKey(),
                    'alert_destination_id' => (int) $destination->getKey(),
                    'event_type' => $eventType,
                    'severity' => $eventSeverity,
                    'status' => $status,
                    'fingerprint_hash' => $fingerprintHash,
                    'send_after' => $sendAfter,
                    'attempt_count' => 0,
                    'payload' => $this->buildPayload($event),
                ]);

                $createdDeliveries++;
            }
        }

        return $createdDeliveries;
    }

    private function normalizeSeverity(string $severity): string
    {
        $severity = strtolower(trim($severity));

        return in_array($severity, ['low', 'medium', 'high', 'critical'], true)
            ? $severity
            : 'high';
    }

    private function meetsMinimumSeverity(string $eventSeverity, string $minimumSeverity): bool
    {
        $rank = [
            'low' => 1,
            'medium' => 2,
            'high' => 3,
            'critical' => 4,
        ];

        $eventRank = $rank[$eventSeverity] ?? 0;
        $minimumRank = $rank[strtolower(trim($minimumSeverity))] ?? 0;

        return $eventRank >= $minimumRank;
    }

    private function shouldSuppress(
        int $workspaceId,
        int $ruleId,
        int $destinationId,
        string $fingerprintHash,
        int $cooldownSeconds,
        CarbonImmutable $now,
    ): bool {
        if ($cooldownSeconds <= 0) {
            return false;
        }

        $cutoff = $now->subSeconds($cooldownSeconds);

        return AlertDelivery::query()
            ->where('workspace_id', $workspaceId)
            ->where('alert_rule_id', $ruleId)
            ->where('alert_destination_id', $destinationId)
            ->where('fingerprint_hash', $fingerprintHash)
            ->whereNotIn('status', [
                AlertDelivery::STATUS_SUPPRESSED,
                AlertDelivery::STATUS_CANCELED,
            ])
            ->where('created_at', '>=', $cutoff)
            ->exists();
    }

    /**
     * @param  array<string, mixed>  $event
     * @return array<string, mixed>
     */
    private function buildPayload(array $event): array
    {
        $title = trim((string) ($event['title'] ?? 'Alert'));
        $body = trim((string) ($event['body'] ?? 'A matching alert event was detected.'));

        if ($title === '') {
            $title = 'Alert';
        }

        if ($body === '') {
            $body = 'A matching alert event was detected.';
        }

        $metadata = Arr::get($event, 'metadata', []);

        if (! is_array($metadata)) {
            $metadata = [];
        }

        return [
            'title' => $title,
            'body' => $body,
            'event_type' => trim((string) ($event['event_type'] ?? '')),
            'fingerprint_key' => trim((string) ($event['fingerprint_key'] ?? '')),
            'metadata' => $metadata,
        ];
    }
}
