<?php

declare(strict_types=1);

namespace App\Framework\ErrorAggregation\Alerting;

use App\Framework\Cache\Cache;
use App\Framework\DateTime\Clock;
use App\Framework\ErrorAggregation\AlertUrgency;
use App\Framework\ErrorAggregation\ErrorEvent;
use App\Framework\ErrorAggregation\ErrorPattern;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Queue;

/**
 * Manages alert distribution across multiple channels
 */
final readonly class AlertManager
{
    private const THROTTLE_CACHE_PREFIX = 'alert_throttle:';
    private const ESCALATION_CACHE_PREFIX = 'alert_escalation:';

    /**
     * @var array<string, AlertChannel>
     */
    private array $channels;

    /**
     * @var array<AlertUrgency, array<string>>
     */
    private array $channelsByUrgency;

    public function __construct(
        private Cache $cache,
        private Clock $clock,
        private Queue $retryQueue,
        private ?Logger $logger = null,
        array $channels = [],
        private array $throttleConfig = [],
        private array $escalationConfig = []
    ) {
        $this->channels = [];
        $this->channelsByUrgency = [];

        foreach ($channels as $channel) {
            $this->addChannel($channel);
        }
    }

    /**
     * Adds an alert channel
     */
    public function addChannel(AlertChannel $channel): void
    {
        $this->channels[$channel->getName()] = $channel;

        // Map channels to urgency levels they can handle
        foreach (AlertUrgency::cases() as $urgency) {
            if ($channel->canHandle($urgency)) {
                $this->channelsByUrgency[$urgency->value][] = $channel->getName();
            }
        }
    }

    /**
     * Sends alert for error pattern
     */
    public function sendAlert(ErrorPattern $pattern, ErrorEvent $triggeringEvent, array $context = []): array
    {
        $urgency = $pattern->getAlertUrgency();
        $results = [];

        // Check throttling
        if ($this->isThrottled($pattern, $urgency)) {
            $this->log('info', "Alert throttled for pattern {$pattern->id->toString()}", [
                'pattern_id' => $pattern->id->toString(),
                'urgency' => $urgency->value,
            ]);

            return ['status' => 'throttled', 'channels' => []];
        }

        // Get channels for this urgency level
        $channelNames = $this->channelsByUrgency[$urgency->value] ?? [];

        if (empty($channelNames)) {
            $this->log('warning', "No channels configured for urgency level {$urgency->value}");

            return ['status' => 'no_channels', 'channels' => []];
        }

        $sentChannels = [];
        $failedChannels = [];

        foreach ($channelNames as $channelName) {
            $channel = $this->channels[$channelName];

            try {
                $success = $channel->sendAlert($pattern, $triggeringEvent, $context);

                if ($success) {
                    $sentChannels[] = $channelName;
                    $this->log('info', "Alert sent via {$channelName}", [
                        'pattern_id' => $pattern->id->toString(),
                        'channel' => $channelName,
                    ]);
                } else {
                    $failedChannels[] = $channelName;
                    $this->scheduleRetry($pattern, $triggeringEvent, $channelName, $context);
                }

                $results[$channelName] = [
                    'status' => $success ? 'sent' : 'failed',
                    'delivery_status' => $channel->getLastDeliveryStatus(),
                ];

            } catch (\Throwable $e) {
                $failedChannels[] = $channelName;
                $this->scheduleRetry($pattern, $triggeringEvent, $channelName, $context);

                $results[$channelName] = [
                    'status' => 'error',
                    'error' => $e->getMessage(),
                ];

                $this->log('error', "Alert failed for channel {$channelName}: {$e->getMessage()}", [
                    'pattern_id' => $pattern->id->toString(),
                    'channel' => $channelName,
                    'exception' => $e,
                ]);
            }
        }

        // Update throttling
        $this->updateThrottle($pattern, $urgency);

        // Schedule escalation if needed
        if (! empty($failedChannels) && $urgency->requiresImmediateProcessing()) {
            $this->scheduleEscalation($pattern, $triggeringEvent, $failedChannels, $context);
        }

        return [
            'status' => empty($failedChannels) ? 'success' : 'partial',
            'channels' => $results,
            'sent' => $sentChannels,
            'failed' => $failedChannels,
        ];
    }

    /**
     * Processes alert retry from queue
     */
    public function processRetry(array $retryData): bool
    {
        $patternData = $retryData['pattern'];
        $eventData = $retryData['event'];
        $channelName = $retryData['channel'];
        $context = $retryData['context'] ?? [];
        $attempt = $retryData['attempt'] ?? 1;

        if (! isset($this->channels[$channelName])) {
            $this->log('error', "Channel {$channelName} not found for retry");

            return false;
        }

        $pattern = ErrorPattern::fromArray($patternData);
        $event = ErrorEvent::fromArray($eventData);
        $channel = $this->channels[$channelName];

        try {
            $success = $channel->sendAlert($pattern, $event, $context);

            if ($success) {
                $this->log('info', "Retry successful for channel {$channelName}", [
                    'pattern_id' => $pattern->id->toString(),
                    'attempt' => $attempt,
                ]);

                return true;
            } else {
                $urgency = $pattern->getAlertUrgency();
                $retryStrategy = $urgency->getRetryStrategy();

                if ($attempt < $retryStrategy['attempts']) {
                    // Schedule next retry
                    $nextDelay = $retryStrategy['delays'][$attempt] ?? 300;
                    $this->scheduleRetry($pattern, $event, $channelName, $context, $attempt + 1, $nextDelay);

                    $this->log('info', "Retry {$attempt} failed, scheduling attempt " . ($attempt + 1), [
                        'pattern_id' => $pattern->id->toString(),
                        'channel' => $channelName,
                        'next_delay' => $nextDelay,
                    ]);
                } else {
                    $this->log('error', "All retry attempts exhausted for channel {$channelName}", [
                        'pattern_id' => $pattern->id->toString(),
                        'total_attempts' => $attempt,
                    ]);
                }

                return false;
            }

        } catch (\Throwable $e) {
            $this->log('error', "Retry failed with exception: {$e->getMessage()}", [
                'pattern_id' => $pattern->id->toString(),
                'channel' => $channelName,
                'attempt' => $attempt,
                'exception' => $e,
            ]);

            return false;
        }
    }

    /**
     * Tests all configured channels
     */
    public function testChannels(): array
    {
        $results = [];

        foreach ($this->channels as $name => $channel) {
            try {
                $success = $channel->test();
                $results[$name] = [
                    'status' => $success ? 'ok' : 'failed',
                    'test_time' => $this->clock->now()->format('c'),
                ];

                $this->log('info', "Channel {$name} test: " . ($success ? 'OK' : 'FAILED'));

            } catch (\Throwable $e) {
                $results[$name] = [
                    'status' => 'error',
                    'error' => $e->getMessage(),
                    'test_time' => $this->clock->now()->format('c'),
                ];

                $this->log('error', "Channel {$name} test error: {$e->getMessage()}");
            }
        }

        return $results;
    }

    /**
     * Gets alert statistics
     */
    public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): array
    {
        // This would typically query a database or metrics store
        // For now, return basic info
        return [
            'period' => [
                'from' => $from->format('c'),
                'to' => $to->format('c'),
            ],
            'channels' => array_keys($this->channels),
            'urgency_mapping' => $this->channelsByUrgency,
        ];
    }

    private function isThrottled(ErrorPattern $pattern, AlertUrgency $urgency): bool
    {
        $throttleKey = self::THROTTLE_CACHE_PREFIX . $pattern->fingerprint;
        $lastAlert = $this->cache->get($throttleKey);

        if ($lastAlert === null) {
            return false;
        }

        $throttleWindow = $this->getThrottleWindow($urgency);
        $now = $this->clock->now()->getTimestamp();

        return ($now - $lastAlert) < $throttleWindow;
    }

    private function updateThrottle(ErrorPattern $pattern, AlertUrgency $urgency): void
    {
        $throttleKey = self::THROTTLE_CACHE_PREFIX . $pattern->fingerprint;
        $throttleWindow = $this->getThrottleWindow($urgency);

        $this->cache->set($throttleKey, $this->clock->now()->getTimestamp(), $throttleWindow);
    }

    private function getThrottleWindow(AlertUrgency $urgency): int
    {
        return $this->throttleConfig[$urgency->value] ?? match ($urgency) {
            AlertUrgency::URGENT => 300,    // 5 minutes
            AlertUrgency::HIGH => 900,      // 15 minutes
            AlertUrgency::MEDIUM => 3600,   // 1 hour
            AlertUrgency::LOW => 86400,     // 24 hours
        };
    }

    private function scheduleRetry(
        ErrorPattern $pattern,
        ErrorEvent $event,
        string $channelName,
        array $context,
        int $attempt = 1,
        ?int $delay = null
    ): void {
        $urgency = $pattern->getAlertUrgency();
        $retryStrategy = $urgency->getRetryStrategy();

        $delay ??= $retryStrategy['delays'][$attempt - 1] ?? 300;

        $retryData = [
            'pattern' => $pattern->toArray(),
            'event' => $event->toArray(),
            'channel' => $channelName,
            'context' => $context,
            'attempt' => $attempt,
            'scheduled_at' => $this->clock->now()->format('c'),
        ];

        $this->retryQueue->pushDelayed('alert_retries', $retryData, $delay);
    }

    private function scheduleEscalation(
        ErrorPattern $pattern,
        ErrorEvent $event,
        array $failedChannels,
        array $context
    ): void {
        $escalationKey = self::ESCALATION_CACHE_PREFIX . $pattern->id->toString();

        // Don't escalate if already escalated recently
        if ($this->cache->get($escalationKey) !== null) {
            return;
        }

        $urgency = $pattern->getAlertUrgency();
        $escalationTimeout = $urgency->getEscalationTimeout();

        $escalationData = [
            'pattern' => $pattern->toArray(),
            'event' => $event->toArray(),
            'failed_channels' => $failedChannels,
            'context' => $context,
            'scheduled_at' => $this->clock->now()->format('c'),
        ];

        $this->retryQueue->pushDelayed('alert_escalations', $escalationData, $escalationTimeout);

        // Mark as escalated
        $this->cache->set($escalationKey, true, $escalationTimeout * 2);

        $this->log('warning', "Alert escalation scheduled", [
            'pattern_id' => $pattern->id->toString(),
            'failed_channels' => $failedChannels,
            'escalation_timeout' => $escalationTimeout,
        ]);
    }

    private function log(string $level, string $message, array $context = []): void
    {
        if ($this->logger) {
            match ($level) {
                'debug' => $this->logger->debug("[AlertManager] {$message}", $context),
                'info' => $this->logger->info("[AlertManager] {$message}", $context),
                'warning' => $this->logger->warning("[AlertManager] {$message}", $context),
                'error' => $this->logger->error("[AlertManager] {$message}", $context),
                default => $this->logger->info("[AlertManager] {$message}", $context),
            };
        }
    }
}
