<?php

declare(strict_types=1);

namespace App\Framework\Discovery\Cache;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Discovery\Results\AttributeRegistry;
use App\Framework\Discovery\Results\DiscoveryRegistry;
use App\Framework\Discovery\Results\InterfaceRegistry;
use App\Framework\Discovery\Results\TemplateRegistry;

/**
 * Manages caching of individual registries to avoid serialization issues
 * Each registry type is cached separately for better performance and reliability
 */
final readonly class RegistryCacheManager
{
    private const string CACHE_PREFIX = 'discovery_registry';
    private const int CACHE_TTL = 3600; // 1 hour

    public function __construct(
        private Cache $cache
    ) {
    }

    /**
     * Cache a DiscoveryRegistry by storing each sub-registry separately
     */
    public function set(string $key, DiscoveryRegistry $registry): bool
    {
        try {
            $success = true;

            // Cache each registry separately
            $success = $this->cacheAttributes($key, $registry->attributes) && $success;
            $success = $this->cacheInterfaces($key, $registry->interfaces) && $success;
            $success = $this->cacheTemplates($key, $registry->templates) && $success;

            // Store metadata about the cached registries
            $metadata = [
                'cached_at' => time(),
                'has_attributes' => count($registry->attributes->getAllTypes()) > 0,
                'has_interfaces' => $registry->interfaces->count() > 0,
                'has_templates' => $registry->templates->count() > 0,
            ];

            $metadataItem = CacheItem::forSet(
                $this->buildKey($key, 'metadata'),
                $metadata,
                Duration::fromSeconds(self::CACHE_TTL)
            );
            $this->cache->set($metadataItem);

            return $success;
        } catch (\Throwable $e) {
            error_log("RegistryCacheManager::set failed: " . $e->getMessage());

            return false;
        }
    }

    /**
     * Retrieve a DiscoveryRegistry from cache by loading each sub-registry
     */
    public function get(string $key): ?DiscoveryRegistry
    {
        try {
            // Check if cache exists via metadata
            $metadataResult = $this->cache->get($this->buildKey($key, 'metadata'));
            $metadataItem = $metadataResult->getItem($this->buildKey($key, 'metadata'));

            if (! $metadataItem->isHit) {
                return null;
            }

            // Load each registry from cache
            $attributes = $this->getCachedAttributes($key);
            $interfaces = $this->getCachedInterfaces($key);
            $templates = $this->getCachedTemplates($key);

            // If any critical registry is missing, consider it a cache miss
            if ($attributes === null || $interfaces === null) {
                $this->invalidate($key);

                return null;
            }

            return new DiscoveryRegistry(
                attributes: $attributes,
                interfaces: $interfaces,
                templates: $templates
            );

        } catch (\Throwable $e) {
            error_log("RegistryCacheManager::get failed: " . $e->getMessage());

            return null;
        }
    }

    /**
     * Invalidate all cached registries for a key
     */
    public function invalidate(string $key): bool
    {
        try {
            $success = true;

            $success = $this->cache->forget($this->buildKey($key, 'attributes')) && $success;
            $success = $this->cache->forget($this->buildKey($key, 'interfaces')) && $success;
            $success = $this->cache->forget($this->buildKey($key, 'templates')) && $success;
            $success = $this->cache->forget($this->buildKey($key, 'metadata')) && $success;

            return $success;
        } catch (\Throwable $e) {
            return false;
        }
    }

    /**
     * Clear all registry caches
     */
    public function flush(): bool
    {
        try {
            // Common cache keys to clear
            $keys = ['default', 'web-request', 'cli-script', 'worker'];

            foreach ($keys as $key) {
                // Ignore individual failures - cache keys might not exist
                $this->invalidate($key);
            }

            // Always return true - clearing non-existent cache is success
            return true;
        } catch (\Throwable $e) {
            return false;
        }
    }

    // === Private Helper Methods ===

    private function cacheRegistry(string $key, string $type, object $registry): bool
    {
        $cacheItem = CacheItem::forSet(
            $this->buildKey($key, $type),
            serialize($registry),
            Duration::fromSeconds(self::CACHE_TTL)
        );

        return $this->cache->set($cacheItem);
    }

    private function cacheAttributes(string $key, AttributeRegistry $registry): bool
    {
        return $this->cacheRegistry($key, 'attributes', $registry);
    }

    private function cacheInterfaces(string $key, InterfaceRegistry $registry): bool
    {
        return $this->cacheRegistry($key, 'interfaces', $registry);
    }

    private function cacheTemplates(string $key, TemplateRegistry $registry): bool
    {
        return $this->cacheRegistry($key, 'templates', $registry);
    }

    private function getCachedRegistry(string $key, string $type, string $expectedClass): ?object
    {
        $result = $this->cache->get($this->buildKey($key, $type));
        $item = $result->getItem($this->buildKey($key, $type));

        if (! $item->isHit) {
            return null;
        }

        $data = $item->value;
        if (! is_string($data)) {
            return null;
        }

        $unserialized = unserialize($data);
        if (! $unserialized instanceof $expectedClass) {
            return null;
        }

        return $unserialized;
    }

    private function getCachedAttributes(string $key): ?AttributeRegistry
    {
        return $this->getCachedRegistry($key, 'attributes', AttributeRegistry::class);
    }

    private function getCachedInterfaces(string $key): ?InterfaceRegistry
    {
        return $this->getCachedRegistry($key, 'interfaces', InterfaceRegistry::class);
    }

    private function getCachedTemplates(string $key): ?TemplateRegistry
    {
        return $this->getCachedRegistry($key, 'templates', TemplateRegistry::class);
    }

    private function buildKey(string $key, string $type): CacheKey
    {
        return CacheKey::fromString(self::CACHE_PREFIX . '_' . $type . '_' . $key);
    }

    private function extractValue($cacheItem): mixed
    {
        $value = $cacheItem->value;

        // Handle nested CacheItems
        $maxDepth = 5;
        $depth = 0;

        while (is_object($value) && property_exists($value, 'value') && $depth < $maxDepth) {
            $value = $value->value;
            $depth++;
        }

        return $value;
    }
}
