<?php

declare(strict_types=1);

namespace App\Framework\Database\Cache;

use App\Framework\Cache\Driver\RedisCache;
use App\Framework\Core\Events\EventDispatcher;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\Events\EntityCreatedEvent;
use App\Framework\Database\Events\EntityDeletedEvent;
use App\Framework\Database\Events\EntityUpdatedEvent;
use App\Framework\Database\Serialization\EntitySerializer;

/**
 * Redis-based second-level cache implementation
 */
final class RedisSecondLevelCache implements SecondLevelCacheInterface
{
    /** @var array<string, CacheRegion> */
    private array $regions = [];

    /** @var array<string, int> Cache statistics */
    private array $stats = [
        'hits' => 0,
        'misses' => 0,
        'puts' => 0,
        'evictions' => 0,
        'query_hits' => 0,
        'query_misses' => 0,
    ];

    public function __construct(
        private readonly RedisCache $cache,
        private readonly EntitySerializer $serializer,
        private readonly EventDispatcher $eventDispatcher,
        private readonly bool $enabled = true
    ) {
        $this->initializeDefaultRegions();
        $this->registerEventListeners();
    }

    public function putEntity(string $entityClass, mixed $entityId, object $entity, ?Duration $ttl = null): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return false;
        }

        $cacheKey = $region->getEntityKey($entityClass, $entityId);
        $serializedEntity = $this->serializer->serialize($entity);
        $effectiveTtl = $ttl ?? $region->defaultTtl;

        $success = $this->cache->set($cacheKey, $serializedEntity, $effectiveTtl->toCacheSeconds());

        if ($success) {
            $this->stats['puts']++;
        }

        return $success;
    }

    public function getEntity(string $entityClass, mixed $entityId): ?object
    {
        if (! $this->enabled) {
            return null;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return null;
        }

        $cacheKey = $region->getEntityKey($entityClass, $entityId);
        $cacheItem = $this->cache->get($cacheKey);

        if ($cacheItem->isHit) {
            $this->stats['hits']++;

            return $this->serializer->deserialize($cacheItem->getValue(), $entityClass);
        }

        $this->stats['misses']++;

        return null;
    }

    public function hasEntity(string $entityClass, mixed $entityId): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return false;
        }

        $cacheKey = $region->getEntityKey($entityClass, $entityId);

        return $this->cache->has($cacheKey);
    }

    public function evictEntity(string $entityClass, mixed $entityId): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        $cacheKey = $region->getEntityKey($entityClass, $entityId);

        $success = $this->cache->forget($cacheKey);

        if ($success) {
            $this->stats['evictions']++;
        }

        return $success;
    }

    public function putQueryResult(QueryCacheKey $key, array $result, ?Duration $ttl = null): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $cacheKey = $key->toString();
        $serializedResult = serialize($result);
        $effectiveTtl = $ttl ?? Duration::fromMinutes(15); // Default query cache TTL

        return $this->cache->set($cacheKey, $serializedResult, $effectiveTtl->toCacheSeconds());
    }

    public function getQueryResult(QueryCacheKey $key): ?array
    {
        if (! $this->enabled) {
            return null;
        }

        $cacheKey = $key->toString();
        $cacheItem = $this->cache->get($cacheKey);

        if ($cacheItem->isHit) {
            $this->stats['query_hits']++;

            return unserialize($cacheItem->getValue());
        }

        $this->stats['query_misses']++;

        return null;
    }

    public function hasQueryResult(QueryCacheKey $key): bool
    {
        if (! $this->enabled) {
            return false;
        }

        return $this->cache->has($key->toString());
    }

    public function evictQueryResult(QueryCacheKey $key): bool
    {
        if (! $this->enabled) {
            return false;
        }

        return $this->cache->forget($key->toString());
    }

    public function putCollection(string $entityClass, array $entities, string $cacheKey, ?Duration $ttl = null): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return false;
        }

        $collectionKey = $region->getCollectionKey($entityClass, $cacheKey);
        $serializedEntities = array_map([$this->serializer, 'serialize'], $entities);
        $effectiveTtl = $ttl ?? $region->defaultTtl;

        return $this->cache->set($collectionKey, serialize($serializedEntities), $effectiveTtl->toCacheSeconds());
    }

    public function getCollection(string $entityClass, string $cacheKey): ?array
    {
        if (! $this->enabled) {
            return null;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return null;
        }

        $collectionKey = $region->getCollectionKey($entityClass, $cacheKey);
        $cacheItem = $this->cache->get($collectionKey);

        if ($cacheItem->isHit) {
            $serializedEntities = unserialize($cacheItem->getValue());

            return array_map(fn ($data) => $this->serializer->deserialize($data, $entityClass), $serializedEntities);
        }

        return null;
    }

    public function hasCollection(string $entityClass, string $cacheKey): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        if (! $region->enabled) {
            return false;
        }

        $collectionKey = $region->getCollectionKey($entityClass, $cacheKey);

        return $this->cache->has($collectionKey);
    }

    public function evictCollection(string $entityClass, string $cacheKey): bool
    {
        if (! $this->enabled) {
            return false;
        }

        $region = $this->getRegionForEntity($entityClass);
        $collectionKey = $region->getCollectionKey($entityClass, $cacheKey);

        return $this->cache->forget($collectionKey);
    }

    public function evictEntityClass(string $entityClass): int
    {
        if (! $this->enabled) {
            return 0;
        }

        $region = $this->getRegionForEntity($entityClass);
        $pattern = $region->getEntityClassPattern($entityClass);

        // Redis-specific pattern invalidation
        return $this->invalidatePattern($pattern);
    }

    public function clear(): bool
    {
        if (! $this->enabled) {
            return false;
        }

        return $this->cache->clear();
    }

    public function getStats(): array
    {
        return $this->stats;
    }

    public function getRegionStats(): array
    {
        $regionStats = [];

        foreach ($this->regions as $name => $region) {
            $regionStats[$name] = [
                'enabled' => $region->enabled,
                'default_ttl' => $region->defaultTtl->toSeconds(),
                'max_size' => $region->maxSize,
            ];
        }

        return $regionStats;
    }

    /**
     * Register cache regions for different entity types
     */
    public function registerRegion(string $entityClass, CacheRegion $region): void
    {
        $this->regions[$entityClass] = $region;
    }

    /**
     * Get cache region for entity class
     */
    private function getRegionForEntity(string $entityClass): CacheRegion
    {
        return $this->regions[$entityClass] ?? $this->regions['default'];
    }

    /**
     * Initialize default cache regions
     */
    private function initializeDefaultRegions(): void
    {
        $this->regions = [
            'default' => CacheRegion::slowChanging('default'),
            'User' => CacheRegion::fastChanging('users'),
            'Session' => CacheRegion::fastChanging('sessions'),
            'Config' => CacheRegion::readOnly('config'),
        ];
    }

    /**
     * Register event listeners for cache invalidation
     */
    private function registerEventListeners(): void
    {
        $this->eventDispatcher->listen(EntityCreatedEvent::class, [$this, 'onEntityCreated']);
        $this->eventDispatcher->listen(EntityUpdatedEvent::class, [$this, 'onEntityUpdated']);
        $this->eventDispatcher->listen(EntityDeletedEvent::class, [$this, 'onEntityDeleted']);
    }

    /**
     * Handle entity creation event
     */
    public function onEntityCreated(EntityCreatedEvent $event): void
    {
        // Invalidate collection caches for this entity type
        $this->invalidateCollectionCaches($event->entityClass);
    }

    /**
     * Handle entity update event
     */
    public function onEntityUpdated(EntityUpdatedEvent $event): void
    {
        // Evict the specific entity from cache
        $this->evictEntity($event->entityClass, $event->entityId);

        // Invalidate collection caches for this entity type
        $this->invalidateCollectionCaches($event->entityClass);
    }

    /**
     * Handle entity deletion event
     */
    public function onEntityDeleted(EntityDeletedEvent $event): void
    {
        // Evict the specific entity from cache
        $this->evictEntity($event->entityClass, $event->entityId);

        // Invalidate collection caches for this entity type
        $this->invalidateCollectionCaches($event->entityClass);
    }

    /**
     * Invalidate collection caches for entity class
     */
    private function invalidateCollectionCaches(string $entityClass): void
    {
        $region = $this->getRegionForEntity($entityClass);
        $pattern = $region->getKeyPrefix() . 'collection:' . $this->getShortClassName($entityClass) . ':*';

        $this->invalidatePattern($pattern);
    }

    /**
     * Invalidate cache entries by pattern
     */
    private function invalidatePattern(string $pattern): int
    {
        // This would need Redis-specific implementation
        // For now, we'll use a placeholder
        return 0;
    }

    private function getShortClassName(string $entityClass): string
    {
        $parts = explode('\\', $entityClass);

        return end($parts);
    }
}
