<?php

declare(strict_types=1);

namespace Tests\Unit\ErrorHandling;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheIdentifier;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheResult;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\DateTime\Clock;
use App\Framework\ErrorAggregation\ErrorAggregator;
use App\Framework\ErrorAggregation\ErrorEvent;
use App\Framework\ErrorAggregation\Storage\InMemoryErrorStorage;
use App\Framework\Exception\Core\CacheErrorCode;
use App\Framework\Exception\Core\DatabaseErrorCode;
use App\Framework\Exception\Core\ErrorSeverity;
use App\Framework\Exception\Core\SystemErrorCode;
use App\Framework\Exception\ErrorHandlerContext;
use App\Framework\Exception\ExceptionContext;
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\RequestContext;
use App\Framework\Exception\SystemContext;
use App\Framework\Queue\InMemoryQueue;

// Helper function für Mock-Objekte
function createTestCache(): Cache
{
    return new class implements Cache {
        private array $data = [];

        public function get(CacheIdentifier ...$identifiers): CacheResult
        {
            $items = [];

            foreach ($identifiers as $identifier) {
                if (isset($this->data[$identifier->toString()])) {
                    $items[] = $this->data[$identifier->toString()];
                } else {
                    $items[] = CacheItem::miss($identifier instanceof CacheKey ? $identifier : CacheKey::fromString($identifier->toString()));
                }
            }

            return CacheResult::fromItems(...$items);
        }

        public function set(CacheItem ...$items): bool
        {
            foreach ($items as $item) {
                $this->data[$item->key->toString()] = $item;
            }
            return true;
        }

        public function has(CacheIdentifier ...$identifiers): array
        {
            $result = [];
            foreach ($identifiers as $identifier) {
                $key = (string) $identifier;
                $result[$key] = isset($this->data[$key]);
            }
            return $result;
        }

        public function forget(CacheIdentifier ...$identifiers): bool
        {
            foreach ($identifiers as $identifier) {
                $key = (string) $identifier;
                unset($this->data[$key]);
            }
            return true;
        }

        public function clear(): bool
        {
            $this->data = [];
            return true;
        }

        public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
        {
            $keyStr = $key->toString();

            if (isset($this->data[$keyStr])) {
                return $this->data[$keyStr];
            }

            $value = $callback();
            $item = CacheItem::forSetting($key, $value, $ttl);
            $this->data[$keyStr] = $item;

            return $item;
        }
    };
}

function createTestClock(): Clock
{
    return new class implements Clock {
        public function now(): \DateTimeImmutable
        {
            return new \DateTimeImmutable();
        }

        public function fromTimestamp(Timestamp $timestamp): \DateTimeImmutable
        {
            return $timestamp->toDateTime();
        }

        public function fromString(string $dateTime, ?string $format = null): \DateTimeImmutable
        {
            return new \DateTimeImmutable($dateTime);
        }

        public function today(): \DateTimeImmutable
        {
            return new \DateTimeImmutable('today');
        }

        public function yesterday(): \DateTimeImmutable
        {
            return new \DateTimeImmutable('yesterday');
        }

        public function tomorrow(): \DateTimeImmutable
        {
            return new \DateTimeImmutable('tomorrow');
        }

        public function time(): Timestamp
        {
            return Timestamp::now();
        }
    };
}

describe('ErrorHandler ErrorAggregator Integration', function () {
    it('processes ErrorHandlerContext through ErrorAggregator', function () {
        // Setup ErrorAggregator
        $storage = new InMemoryErrorStorage();
        $cache = createTestCache();
        $clock = createTestClock();
        $alertQueue = new InMemoryQueue();

        $errorAggregator = new ErrorAggregator(
            storage: $storage,
            cache: $cache,
            clock: $clock,
            alertQueue: $alertQueue
        );

        // Create ErrorHandlerContext (simulates what ErrorHandler does)
        $exception = FrameworkException::create(
            DatabaseErrorCode::QUERY_FAILED,
            'Test database query failed'
        );

        $exceptionContext = ExceptionContext::empty()
            ->withOperation('test_operation', 'TestComponent')
            ->withData([
                'test_key' => 'test_value',
                'original_exception' => $exception,  // Store exception for message extraction
                'exception_message' => $exception->getMessage()
            ]);

        $requestContext = RequestContext::fromGlobals();
        $systemContext = SystemContext::current();

        $errorHandlerContext = ErrorHandlerContext::create(
            $exceptionContext,
            $requestContext,
            $systemContext,
            ['http_status' => 500]
        );

        // Process through ErrorAggregator (simulates ErrorHandler calling it)
        $errorAggregator->processError($errorHandlerContext);

        // Verify error was stored
        $recentEvents = $errorAggregator->getRecentEvents(10);
        expect($recentEvents)->toHaveCount(1);

        $event = $recentEvents[0];
        expect($event->errorMessage)->toBe('Test database query failed');
        expect($event->severity)->toBe(ErrorSeverity::ERROR);
        expect($event->component)->toBe('TestComponent');
        expect($event->operation)->toBe('test_operation');

        // Verify pattern was created
        $activePatterns = $errorAggregator->getActivePatterns(10);
        expect($activePatterns)->toHaveCount(1);

        $pattern = $activePatterns[0];
        expect($pattern->occurrenceCount)->toBe(1);
        expect($pattern->severity)->toBe(ErrorSeverity::ERROR);
        expect($pattern->component)->toBe('TestComponent');
    });

    it('creates error patterns from multiple identical errors', function () {
        $storage = new InMemoryErrorStorage();
        $cache = createTestCache();
        $clock = createTestClock();
        $alertQueue = new InMemoryQueue();

        $errorAggregator = new ErrorAggregator(
            storage: $storage,
            cache: $cache,
            clock: $clock,
            alertQueue: $alertQueue
        );

        // Process same error 3 times
        for ($i = 0; $i < 3; $i++) {
            $exception = FrameworkException::create(
                DatabaseErrorCode::QUERY_FAILED,
                'Repeated database error'
            );

            $exceptionContext = ExceptionContext::empty()
                ->withOperation('query_execution', 'DatabaseManager')
                ->withData([
                    'query' => 'SELECT * FROM users',
                    'original_exception' => $exception,
                    'exception_message' => $exception->getMessage()
                ]);

            $requestContext = RequestContext::fromGlobals();
            $systemContext = SystemContext::current();

            $errorHandlerContext = ErrorHandlerContext::create(
                $exceptionContext,
                $requestContext,
                $systemContext,
                ['http_status' => 500]
            );

            $errorAggregator->processError($errorHandlerContext);
        }

        // Verify all 3 events were stored
        $recentEvents = $errorAggregator->getRecentEvents(10);
        expect($recentEvents)->toHaveCount(3);

        // Verify single pattern with 3 occurrences (same fingerprint)
        $activePatterns = $errorAggregator->getActivePatterns(10);
        expect($activePatterns)->toHaveCount(1);

        $pattern = $activePatterns[0];
        expect($pattern->occurrenceCount)->toBe(3);
        expect($pattern->component)->toBe('DatabaseManager');
        expect($pattern->operation)->toBe('query_execution');
    });

    it('handles ErrorAggregator being null (optional dependency)', function () {
        $errorAggregator = null;

        // Simulate the nullable call pattern used in ErrorHandler
        $exception = FrameworkException::create(
            DatabaseErrorCode::QUERY_FAILED,
            'Test error'
        );

        $exceptionContext = ExceptionContext::empty();
        $requestContext = RequestContext::fromGlobals();
        $systemContext = SystemContext::current();

        $errorHandlerContext = ErrorHandlerContext::create(
            $exceptionContext,
            $requestContext,
            $systemContext,
            []
        );

        // This should not throw an error
        $errorAggregator?->processError($errorHandlerContext);

        // If we get here, the null-safe operator worked correctly
        expect(true)->toBeTrue();
    });

    it('processes errors with different severities correctly', function () {
        $storage = new InMemoryErrorStorage();
        $cache = createTestCache();
        $clock = createTestClock();
        $alertQueue = new InMemoryQueue();

        $errorAggregator = new ErrorAggregator(
            storage: $storage,
            cache: $cache,
            clock: $clock,
            alertQueue: $alertQueue
        );

        // Process errors with different severities
        $errorCodes = [
            SystemErrorCode::RESOURCE_EXHAUSTED,  // CRITICAL
            DatabaseErrorCode::QUERY_FAILED,      // ERROR
            CacheErrorCode::READ_FAILED,          // WARNING
        ];

        foreach ($errorCodes as $errorCode) {
            $exception = FrameworkException::create($errorCode, 'Test message');

            $exceptionContext = ExceptionContext::empty()
                ->withOperation('test_op', 'TestComponent')
                ->withData([
                    'original_exception' => $exception,
                    'exception_message' => $exception->getMessage()
                ]);

            $requestContext = RequestContext::fromGlobals();
            $systemContext = SystemContext::current();

            $errorHandlerContext = ErrorHandlerContext::create(
                $exceptionContext,
                $requestContext,
                $systemContext,
                []
            );

            $errorAggregator->processError($errorHandlerContext);
        }

        // Verify all events were stored
        $recentEvents = $errorAggregator->getRecentEvents(10);
        expect($recentEvents)->toHaveCount(3);

        // Verify patterns reflect correct severities
        $activePatterns = $errorAggregator->getActivePatterns(10);
        expect($activePatterns)->toHaveCount(3);

        $severities = array_map(fn($p) => $p->severity, $activePatterns);
        expect($severities)->toContain(ErrorSeverity::CRITICAL);
        expect($severities)->toContain(ErrorSeverity::ERROR);
        expect($severities)->toContain(ErrorSeverity::WARNING);
    });
});
