<?php

namespace App\Services\Alerts;

use App\Models\AlertLog;
use App\Models\AlertRule;
use App\Models\Client;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;

class AlertEngine
{
    public function __construct(
        private readonly AlertDateRangeResolver $dateRangeResolver,
        private readonly AlertMetricCalculator $metricCalculator,
        private readonly AlertNotificationService $notificationService,
    ) {
    }

    /**
     * @return array{evaluated:int, triggered:int, skipped:int, logs:array<int, int>}
     */
    public function evaluateAll(?int $clientId = null, ?int $ruleId = null, bool $notify = true): array
    {
        $rules = AlertRule::query()
            ->with('client')
            ->where('is_active', true)
            ->when($ruleId, fn ($query) => $query->whereKey($ruleId))
            ->when($clientId, fn ($query) => $query->where(function ($query) use ($clientId) {
                $query->where('client_id', $clientId)->orWhereNull('client_id');
            }))
            ->get();

        $summary = ['evaluated' => 0, 'triggered' => 0, 'skipped' => 0, 'logs' => []];

        foreach ($rules as $rule) {
            foreach ($this->clientsForRule($rule, $clientId) as $client) {
                $summary['evaluated']++;
                $log = $this->evaluateRuleForClient($rule, $client, $notify);

                if ($log instanceof AlertLog) {
                    $summary['triggered']++;
                    $summary['logs'][] = $log->id;
                } else {
                    $summary['skipped']++;
                }
            }
        }

        return $summary;
    }

    public function evaluateRuleForClient(AlertRule $rule, Client $client, bool $notify = true): ?AlertLog
    {
        [$start, $end] = $this->dateRangeResolver->resolve($rule->period);
        $value = $this->metricCalculator->calculate($client->id, $rule->platform, $rule->metric, $start, $end);
        $threshold = (float) $rule->threshold;
        $triggered = $this->compare($value, $rule->operator, $threshold);

        $rule->forceFill([
            'last_evaluated_at' => now(),
            'last_value' => $value,
            'last_status' => $triggered ? 'triggered' : 'ok',
        ])->save();

        if (! $triggered) {
            return null;
        }

        $fingerprint = sha1(implode('|', [
            $rule->id,
            $client->id,
            $rule->platform,
            $rule->metric,
            $rule->period,
            $start->toDateString(),
            $end->toDateString(),
        ]));

        $existing = AlertLog::where('fingerprint', $fingerprint)->first();
        if ($existing instanceof AlertLog) {
            return null;
        }

        if ($this->hasCooldownLog($rule, $client)) {
            $rule->update(['last_status' => 'cooldown']);
            return null;
        }

        $metricLabel = AlertMetricCatalog::label($rule->platform, $rule->metric);
        $platformLabel = strtoupper(str_replace('_', ' ', $rule->platform));

        $message = sprintf(
            '%s için %s metriği %s %s koşulunu sağladı. Güncel değer: %s. Dönem: %s - %s.',
            $client->company_name,
            $metricLabel,
            $rule->operator,
            $this->formatNumber($threshold),
            $this->formatNumber($value),
            $start->format('d.m.Y'),
            $end->format('d.m.Y')
        );

        $log = AlertLog::create([
            'alert_rule_id' => $rule->id,
            'client_id' => $client->id,
            'platform' => $rule->platform,
            'title' => $rule->name . ' - ' . $platformLabel,
            'message' => $message,
            'severity' => $rule->severity,
            'metric' => $rule->metric,
            'operator' => $rule->operator,
            'threshold' => $threshold,
            'current_value' => $value,
            'period' => $rule->period,
            'period_start' => $start->toDateString(),
            'period_end' => $end->toDateString(),
            'status' => 'open',
            'fingerprint' => $fingerprint,
            'payload' => [
                'metric_label' => $metricLabel,
                'platform_label' => $platformLabel,
                'rule' => $rule->only(['id', 'name', 'period', 'severity', 'cooldown_minutes']),
            ],
            'triggered_at' => now(),
        ]);

        $rule->update([
            'last_triggered_at' => now(),
            'last_status' => 'triggered',
        ]);

        if ($notify && $rule->hasChannel('email')) {
            $this->notificationService->send($log);
        }

        return $log;
    }

    /**
     * @return Collection<int, Client>
     */
    private function clientsForRule(AlertRule $rule, ?int $clientId): Collection
    {
        if ($rule->client_id) {
            return Client::query()
                ->whereKey($rule->client_id)
                ->when($clientId, fn ($query) => $query->whereKey($clientId))
                ->get();
        }

        return Client::query()
            ->where('status', 'active')
            ->when($clientId, fn ($query) => $query->whereKey($clientId))
            ->orderBy('company_name')
            ->get();
    }

    private function compare(float $value, string $operator, float $threshold): bool
    {
        return match ($operator) {
            '>' => $value > $threshold,
            '>=' => $value >= $threshold,
            '<' => $value < $threshold,
            '<=' => $value <= $threshold,
            '=' => abs($value - $threshold) < 0.000001,
            '!=' => abs($value - $threshold) >= 0.000001,
            default => false,
        };
    }

    private function hasCooldownLog(AlertRule $rule, Client $client): bool
    {
        $cooldown = max((int) $rule->cooldown_minutes, 0);

        if ($cooldown === 0) {
            return false;
        }

        return AlertLog::query()
            ->where('alert_rule_id', $rule->id)
            ->where('client_id', $client->id)
            ->where('triggered_at', '>=', now()->subMinutes($cooldown))
            ->exists();
    }

    private function formatNumber(float $number): string
    {
        return Str::of(number_format($number, 2, ',', '.'))->replace(',00', '')->toString();
    }
}
