<?php

declare(strict_types=1);

namespace App\Framework\ErrorAggregation;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\DateTime\Clock;
use App\Framework\ErrorAggregation\Storage\ErrorStorageInterface;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Logging\Logger;
use App\Framework\Queue\Queue;

/**
 * Central error aggregation engine
 * Collects, analyzes, and patterns errors for alerting and monitoring
 */
final readonly class ErrorAggregator implements ErrorAggregatorInterface
{
    private const string PATTERN_CACHE_PREFIX = 'error_pattern:';
    private const int PATTERN_CACHE_TTL = 3600; // 1 hour

    public function __construct(
        private ErrorStorageInterface $storage,
        private Cache $cache,
        private Clock $clock,
        private Queue $alertQueue,
        private ?Logger $logger = null,
        private int $batchSize = 100,
        private int $maxRetentionDays = 90,
    ) {
    }

    /**
     * Processes a new error from ErrorHandlerContext
     */
    public function processError(ErrorHandlerContext $context): void
    {
        try {
            $errorEvent = ErrorEvent::fromErrorHandlerContext($context, $this->clock);
            $this->processErrorEvent($errorEvent);
        } catch (\Throwable $e) {
            // Don't let error aggregation break the application
            $this->logError("Failed to process error: " . $e->getMessage(), [
                'exception' => $e,
                'context' => $context->toArray(),
            ]);
        }
    }

    /**
     * Processes an ErrorEvent
     */
    public function processErrorEvent(ErrorEvent $event): void
    {
        // Store the individual error event
        $this->storage->storeEvent($event);

        // Update or create error pattern
        $pattern = $this->updateErrorPattern($event);

        // Check if pattern should trigger alert
        if ($pattern->shouldAlert()) {
            $this->queueAlert($pattern, $event);
        }

        // Log for debugging
        $this->logError("Error processed", [
            'event_id' => (string) $event->id,
            'fingerprint' => $event->getFingerprint(),
            'pattern_id' => (string) $pattern->id,
            'occurrence_count' => $pattern->occurrenceCount,
            'should_alert' => $pattern->shouldAlert(),
        ]);
    }

    /**
     * Updates or creates error pattern for the event
     */
    private function updateErrorPattern(ErrorEvent $event): ErrorPattern
    {
        $fingerprint = $event->getFingerprint();
        $cacheKey = CacheKey::fromString(self::PATTERN_CACHE_PREFIX . $fingerprint);

        // Try to get existing pattern from cache
        $cacheResult = $this->cache->get($cacheKey);
        if ($cacheResult->isHit) {
            // Cache returns the pattern array, we need to deserialize it
            $pattern = ErrorPattern::fromArray($cacheResult->value);
        } else {
            // Try to get from storage
            $pattern = $this->storage->getPatternByFingerprint($fingerprint);
        }

        if ($pattern === null) {
            // Create new pattern
            $pattern = ErrorPattern::fromErrorEvent($event, $this->clock);
        } else {
            // Update existing pattern
            $pattern = $pattern->withNewOccurrence($event);
        }

        // Store updated pattern
        $this->storage->storePattern($pattern);

        // Cache the pattern
        $cacheItem = \App\Framework\Cache\CacheItem::forSet(
            $cacheKey,
            $pattern->toArray(),
            Duration::fromSeconds(self::PATTERN_CACHE_TTL)
        );
        $this->cache->set($cacheItem);

        return $pattern;
    }

    /**
     * Queues alert for pattern
     */
    private function queueAlert(ErrorPattern $pattern, ErrorEvent $triggeringEvent): void
    {
        // Create alert job
        $alertJob = new \App\Framework\ErrorAggregation\Jobs\ErrorPatternAlertJob(
            patternId: (string) $pattern->id,
            eventId: (string) $triggeringEvent->id,
            urgency: $pattern->getAlertUrgency(),
            patternData: $pattern->toArray(),
            triggeringEventData: $triggeringEvent->toArray()
        );

        // Map urgency to queue priority
        $queuePriority = match ($pattern->getAlertUrgency()) {
            AlertUrgency::URGENT => \App\Framework\Queue\ValueObjects\QueuePriority::critical(),
            AlertUrgency::HIGH => \App\Framework\Queue\ValueObjects\QueuePriority::high(),
            AlertUrgency::MEDIUM => \App\Framework\Queue\ValueObjects\QueuePriority::normal(),
            AlertUrgency::LOW => \App\Framework\Queue\ValueObjects\QueuePriority::low(),
        };

        // Create job payload and push to queue
        $payload = \App\Framework\Queue\ValueObjects\JobPayload::create(
            job: $alertJob,
            priority: $queuePriority
        );

        $this->alertQueue->push($payload);

        $this->logError("Alert queued", [
            'pattern_id' => (string) $pattern->id,
            'urgency' => $pattern->getAlertUrgency()->value,
            'occurrence_count' => $pattern->occurrenceCount,
        ]);
    }

    /**
     * Gets error statistics for a time period
     */
    public function getStatistics(\DateTimeImmutable $from, \DateTimeImmutable $to): array
    {
        return $this->storage->getStatistics($from, $to);
    }

    /**
     * Gets active error patterns
     */
    public function getActivePatterns(int $limit = 50, int $offset = 0): array
    {
        return $this->storage->getActivePatterns($limit, $offset);
    }

    /**
     * Gets error patterns by service
     */
    public function getPatternsByService(string $service, int $limit = 50): array
    {
        return $this->storage->getPatternsByService($service, $limit);
    }

    /**
     * Gets recent error events
     */
    public function getRecentEvents(int $limit = 100, ?ErrorSeverity $severity = null): array
    {
        return $this->storage->getRecentEvents($limit, $severity);
    }

    /**
     * Acknowledges an error pattern
     */
    public function acknowledgePattern(string $patternId, string $acknowledgedBy, ?string $resolution = null): bool
    {
        $pattern = $this->storage->getPatternById($patternId);
        if ($pattern === null) {
            return false;
        }

        $acknowledgedPattern = $pattern->acknowledge($acknowledgedBy, $resolution);
        $this->storage->storePattern($acknowledgedPattern);

        // Clear from cache
        $cacheKey = self::PATTERN_CACHE_PREFIX . $pattern->fingerprint;
        $this->cache->delete(CacheKey::fromString($cacheKey));

        $this->logError("Pattern acknowledged", [
            'pattern_id' => $patternId,
            'acknowledged_by' => $acknowledgedBy,
            'resolution' => $resolution,
        ]);

        return true;
    }

    /**
     * Resolves an error pattern
     */
    public function resolvePattern(string $patternId, string $resolution): bool
    {
        $pattern = $this->storage->getPatternById($patternId);
        if ($pattern === null) {
            return false;
        }

        $resolvedPattern = $pattern->resolve($resolution);
        $this->storage->storePattern($resolvedPattern);

        // Clear from cache
        $cacheKey = self::PATTERN_CACHE_PREFIX . $pattern->fingerprint;
        $this->cache->delete(CacheKey::fromString($cacheKey));

        $this->logError("Pattern resolved", [
            'pattern_id' => $patternId,
            'resolution' => $resolution,
        ]);

        return true;
    }

    /**
     * Processes errors in batch for performance
     */
    public function processBatch(array $errorEvents): void
    {
        $batches = array_chunk($errorEvents, $this->batchSize);

        foreach ($batches as $batch) {
            $this->storage->storeEventsBatch($batch);

            foreach ($batch as $event) {
                $this->updateErrorPattern($event);
            }
        }

        $this->logError("Batch processed", [
            'total_events' => count($errorEvents),
            'batches' => count($batches),
        ]);
    }

    /**
     * Cleans up old data based on retention policy
     */
    public function cleanup(): int
    {
        $deletedEvents = 0;
        $deletedPatterns = 0;

        foreach (ErrorSeverity::cases() as $severity) {
            $retentionDays = $severity->getRetentionDays();
            $cutoffDate = $this->clock->now()->sub(new \DateInterval("P{$retentionDays}D"));

            $deletedEvents += $this->storage->deleteOldEvents($cutoffDate, $severity);
        }

        // Delete inactive patterns older than max retention
        $maxCutoff = $this->clock->now()->sub(new \DateInterval("P{$this->maxRetentionDays}D"));
        $deletedPatterns = $this->storage->deleteOldPatterns($maxCutoff);

        $this->logError("Cleanup completed", [
            'deleted_events' => $deletedEvents,
            'deleted_patterns' => $deletedPatterns,
        ]);

        return $deletedEvents + $deletedPatterns;
    }

    /**
     * Gets error trends for analysis
     */
    public function getErrorTrends(
        \DateTimeImmutable $from,
        \DateTimeImmutable $to,
        string $groupBy = 'hour'
    ): array {
        return $this->storage->getErrorTrends($from, $to, $groupBy);
    }

    /**
     * Gets top error patterns by occurrence count
     */
    public function getTopPatterns(int $limit = 10, ?string $service = null): array
    {
        return $this->storage->getTopPatterns($limit, $service);
    }

    /**
     * Exports error data for external analysis
     */
    public function exportData(
        \DateTimeImmutable $from,
        \DateTimeImmutable $to,
        array $filters = []
    ): \Generator {
        return $this->storage->exportEvents($from, $to, $filters);
    }

    /**
     * Gets health status of error aggregation system
     */
    public function getHealthStatus(): array
    {
        try {
            $stats = $this->getStatistics(
                $this->clock->now()->sub(new \DateInterval('PT1H')),
                $this->clock->now()
            );

            return [
                'status' => 'healthy',
                'last_hour_events' => $stats['total_events'] ?? 0,
                'active_patterns' => count($this->getActivePatterns(100)),
                'storage_health' => $this->storage->getHealthStatus(),
                'cache_status' => $this->cache->get(CacheKey::fromString('health_check')) !== null ? 'healthy' : 'degraded',
            ];
        } catch (\Throwable $e) {
            return [
                'status' => 'unhealthy',
                'error' => $e->getMessage(),
            ];
        }
    }

    private function logError(string $message, array $context = []): void
    {
        if ($this->logger) {
            $logContext = !empty($context)
                ? \App\Framework\Logging\ValueObjects\LogContext::withData($context)
                : null;
            $this->logger->info("[ErrorAggregator] {$message}", $logContext);
        }
    }
}
