<?php

declare(strict_types=1);

namespace App\Framework\Database\Cache;

use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Database\IdentityMap;
use App\Framework\Database\Metadata\MetadataRegistry;

/**
 * Manages entity caching with smart invalidation and TTL strategies
 */
final class EntityCacheManager
{
    /** @var array<string, CacheStrategy> Cache strategies per entity type */
    private array $strategies = [];

    public function __construct(
        private readonly SecondLevelCacheInterface $cache,
        private readonly IdentityMap $identityMap,
        private readonly MetadataRegistry $metadataRegistry,
        private readonly bool $enabled = true
    ) {
        $this->initializeDefaultStrategies();
    }

    /**
     * Find entity with cache check
     */
    public function findEntity(string $entityClass, mixed $entityId, callable $databaseLoader): ?object
    {
        if (! $this->enabled) {
            return $databaseLoader();
        }

        // Check identity map first
        if ($this->identityMap->has($entityClass, $entityId)) {
            return $this->identityMap->get($entityClass, $entityId);
        }

        // Check second-level cache
        $cachedEntity = $this->cache->getEntity($entityClass, $entityId);
        if ($cachedEntity !== null) {
            // Add to identity map
            $this->identityMap->add($cachedEntity, $entityId);

            return $cachedEntity;
        }

        // Load from database
        $entity = $databaseLoader();
        if ($entity !== null) {
            // Cache the entity
            $this->cacheEntity($entity);

            // Add to identity map
            $this->identityMap->add($entity, $entityId);
        }

        return $entity;
    }

    /**
     * Find entities collection with caching
     */
    public function findCollection(
        string $entityClass,
        array $criteria,
        ?array $orderBy,
        ?int $limit,
        ?int $offset,
        callable $databaseLoader
    ): array {
        if (! $this->enabled) {
            return $databaseLoader();
        }

        $cacheKey = EntityCacheKey::forCollection($entityClass, $criteria, $orderBy, $limit, $offset);

        // Check cache first
        $cachedCollection = $this->cache->getCollection($entityClass, $cacheKey);
        if ($cachedCollection !== null) {
            // Add entities to identity map
            foreach ($cachedCollection as $entity) {
                $entityId = $this->extractEntityId($entity);
                if (! $this->identityMap->has($entityClass, $entityId)) {
                    $this->identityMap->add($entity, $entityId);
                }
            }

            return $cachedCollection;
        }

        // Load from database
        $entities = $databaseLoader();

        // Cache the collection if it's worth caching
        if ($this->shouldCacheCollection($entities, $criteria)) {
            $ttl = $this->getCollectionCacheTtl($entityClass, count($entities));
            $this->cache->putCollection($entityClass, $entities, $cacheKey, $ttl);
        }

        // Cache individual entities and add to identity map
        foreach ($entities as $entity) {
            $this->cacheEntity($entity);
            $entityId = $this->extractEntityId($entity);
            if (! $this->identityMap->has($entityClass, $entityId)) {
                $this->identityMap->add($entity, $entityId);
            }
        }

        return $entities;
    }

    /**
     * Cache an entity
     */
    public function cacheEntity(object $entity): void
    {
        if (! $this->enabled) {
            return;
        }

        $entityClass = $entity::class;
        $entityId = $this->extractEntityId($entity);

        if ($entityId === null) {
            return; // Cannot cache entities without ID
        }

        $strategy = $this->getStrategyForEntity($entityClass);
        $ttl = $this->getEntityCacheTtl($entityClass);

        $this->cache->putEntity($entityClass, $entityId, $entity, $ttl);
    }

    /**
     * Evict entity from cache
     */
    public function evictEntity(object $entity): void
    {
        if (! $this->enabled) {
            return;
        }

        $entityClass = $entity::class;
        $entityId = $this->extractEntityId($entity);

        if ($entityId !== null) {
            $this->cache->evictEntity($entityClass, $entityId);
        }

        // Also evict from identity map
        $this->identityMap->remove($entityClass, $entityId);
    }

    /**
     * Evict all entities of a class
     */
    public function evictEntityClass(string $entityClass): int
    {
        if (! $this->enabled) {
            return 0;
        }

        // Clear from identity map
        $this->identityMap->clear($entityClass);

        // Clear from second-level cache
        return $this->cache->evictEntityClass($entityClass);
    }

    /**
     * Warm up cache with commonly accessed entities
     */
    public function warmUpCache(array $warmUpStrategies): void
    {
        if (! $this->enabled) {
            return;
        }

        foreach ($warmUpStrategies as $entityClass => $strategy) {
            $this->executeWarmUpStrategy($entityClass, $strategy);
        }
    }

    /**
     * Get cache hit ratio for performance monitoring
     */
    public function getCacheHitRatio(): float
    {
        $stats = $this->cache->getStats();

        $totalAccess = $stats['hits'] + $stats['misses'];
        if ($totalAccess === 0) {
            return 0.0;
        }

        return $stats['hits'] / $totalAccess;
    }

    /**
     * Register cache strategy for specific entity type
     */
    public function registerStrategy(string $entityClass, CacheStrategy $strategy): void
    {
        $this->strategies[$entityClass] = $strategy;
    }

    /**
     * Extract entity ID using metadata
     */
    private function extractEntityId(object $entity): mixed
    {
        $entityClass = $entity::class;
        $metadata = $this->metadataRegistry->getMetadata($entityClass);

        $idProperty = $metadata->getIdProperty();
        if ($idProperty === null) {
            return null;
        }

        $reflection = new \ReflectionClass($entityClass);
        $property = $reflection->getProperty($idProperty->name);
        $property->setAccessible(true);

        return $property->getValue($entity);
    }

    /**
     * Get cache strategy for entity
     */
    private function getStrategyForEntity(string $entityClass): string
    {
        if (isset($this->strategies[$entityClass])) {
            return $this->strategies[$entityClass];
        }

        return 'default';
    }

    /**
     * Get TTL for entity caching
     */
    private function getEntityCacheTtl(string $entityClass): Duration
    {
        $strategy = $this->getStrategyForEntity($entityClass);

        return match ($strategy) {
            'fast_changing' => Duration::fromMinutes(5),
            'slow_changing' => Duration::fromHours(1),
            'read_only' => Duration::fromHours(24),
            default => Duration::fromMinutes(30)
        };
    }

    /**
     * Get TTL for collection caching
     */
    private function getCollectionCacheTtl(string $entityClass, int $collectionSize): Duration
    {
        $baseTtl = $this->getEntityCacheTtl($entityClass);

        // Larger collections get shorter TTL
        if ($collectionSize > 1000) {
            return Duration::fromSeconds($baseTtl->toSeconds() / 4);
        }

        if ($collectionSize > 100) {
            return Duration::fromSeconds($baseTtl->toSeconds() / 2);
        }

        return $baseTtl;
    }

    /**
     * Determine if collection should be cached
     */
    private function shouldCacheCollection(array $entities, array $criteria): bool
    {
        // Don't cache empty collections
        if (empty($entities)) {
            return false;
        }

        // Don't cache very large collections
        if (count($entities) > 5000) {
            return false;
        }

        // Don't cache collections with complex criteria
        if (count($criteria) > 5) {
            return false;
        }

        return true;
    }

    /**
     * Initialize default cache strategies
     */
    private function initializeDefaultStrategies(): void
    {
        $this->strategies = [
            'User' => 'fast_changing',
            'Session' => 'fast_changing',
            'Config' => 'read_only',
            'Translation' => 'read_only',
            'default' => 'slow_changing',
        ];
    }

    /**
     * Execute warm-up strategy for entity class
     */
    private function executeWarmUpStrategy(string $entityClass, array $strategy): void
    {
        // Implementation would depend on specific warm-up requirements
        // This could include loading frequently accessed entities,
        // pre-computing common queries, etc.
    }

    /**
     * Enable or disable caching
     */
    public function setEnabled(bool $enabled): void
    {
        $this->enabled = $enabled;
    }

    /**
     * Check if caching is enabled
     */
    public function isEnabled(): bool
    {
        return $this->enabled;
    }
}
