<?php

declare(strict_types=1);

namespace App\Framework\Mcp\Core\Services;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Mcp\Core\ValueObjects\CacheMetrics;
use App\Framework\Mcp\Core\ValueObjects\CacheStrategy;

/**
 * Intelligent MCP Cache Manager
 *
 * Advanced caching system specifically designed for MCP tools with
 * intelligent invalidation, compression, and performance optimization.
 * Provides adaptive caching strategies based on tool usage patterns.
 */
final readonly class IntelligentMcpCacheManager
{
    private const DEFAULT_TTL = 3600; // 1 hour
    private const COMPRESSION_THRESHOLD = 50 * 1024; // 50KB
    private const MAX_CACHE_SIZE = 10 * 1024 * 1024; // 10MB
    private const CACHE_PREFIX = 'mcp:';

    public function __construct(
        private Cache $cache,
        private array $strategyConfig = []
    ) {
    }

    /**
     * Get cached result with intelligent strategy selection
     */
    public function get(
        string $toolName,
        string $methodName,
        array $parameters,
        CacheStrategy $strategy = CacheStrategy::ADAPTIVE
    ): mixed {
        $cacheKey = $this->generateCacheKey($toolName, $methodName, $parameters);

        // Check if cache hit and still valid
        $cacheResult = $this->cache->get($cacheKey);
        $cacheItem = $cacheResult->getItem($cacheKey);

        if ($cacheItem->isHit) {
            $this->recordCacheHit($toolName, $methodName);

            return $this->decompressIfNeeded($cacheItem->value);
        }

        $this->recordCacheMiss($toolName, $methodName);

        return null;
    }

    /**
     * Store result with intelligent caching strategy
     */
    public function set(
        string $toolName,
        string $methodName,
        array $parameters,
        mixed $result,
        CacheStrategy $strategy = CacheStrategy::ADAPTIVE
    ): void {
        $cacheKey = $this->generateCacheKey($toolName, $methodName, $parameters);
        $ttl = $this->calculateTTL($toolName, $methodName, $strategy);

        // Compress large results
        $processedResult = $this->compressIfNeeded($result);

        // Apply cache size limits
        $this->enforceMemoryLimits();

        $cacheItem = CacheItem::forSet(
            key: $cacheKey,
            value: $processedResult,
            ttl: Duration::fromSeconds($ttl)
        );

        $this->cache->set($cacheItem);
        $this->recordCacheSet($toolName, $methodName, $processedResult);
    }

    /**
     * Remember pattern - get from cache or execute callback
     */
    public function remember(
        string $toolName,
        string $methodName,
        array $parameters,
        callable $callback,
        CacheStrategy $strategy = CacheStrategy::ADAPTIVE
    ): mixed {
        $cached = $this->get($toolName, $methodName, $parameters, $strategy);

        if ($cached !== null) {
            return $cached;
        }

        $result = $callback();
        $this->set($toolName, $methodName, $parameters, $result, $strategy);

        return $result;
    }

    /**
     * Invalidate cache for specific tool
     */
    public function invalidateTool(string $toolName): void
    {
        $tag = $this->generateToolTag($toolName);
        $this->cache->forget($tag);
        $this->recordInvalidation($toolName);
    }

    /**
     * Invalidate cache for specific method
     */
    public function invalidateMethod(string $toolName, string $methodName): void
    {
        $pattern = $this->generateMethodPattern($toolName, $methodName);
        $this->cache->forgetByPattern($pattern);
        $this->recordInvalidation($toolName, $methodName);
    }

    /**
     * Warm up cache for frequently used tools
     */
    public function warmUp(array $toolMethods): void
    {
        foreach ($toolMethods as $toolName => $methods) {
            foreach ($methods as $methodName => $parameterSets) {
                foreach ($parameterSets as $parameters) {
                    // Skip if already cached
                    $cached = $this->get($toolName, $methodName, $parameters);
                    if ($cached === null) {
                        // This would need to be coordinated with the actual tool execution
                        $this->recordWarmupAttempt($toolName, $methodName);
                    }
                }
            }
        }
    }

    /**
     * Get cache statistics and metrics
     */
    public function getMetrics(): CacheMetrics
    {
        $stats = method_exists($this->cache, 'getStatistics') ? $this->cache->getStatistics() : [];

        return new CacheMetrics(
            hitRate: $this->calculateHitRate(),
            totalHits: $stats['hits'] ?? 0,
            totalMisses: $stats['misses'] ?? 0,
            totalSets: $stats['sets'] ?? 0,
            memoryUsage: $this->calculateMemoryUsage(),
            compressionRatio: $this->calculateCompressionRatio(),
            averageResponseTime: $this->calculateAverageResponseTime(),
            topTools: $this->getTopCachedTools(),
            invalidations: $this->getInvalidationStats()
        );
    }

    /**
     * Optimize cache performance
     */
    public function optimize(): array
    {
        $optimizations = [];

        // Remove stale entries
        $staleRemoved = $this->removeStaleEntries();
        if ($staleRemoved > 0) {
            $optimizations[] = "Removed {$staleRemoved} stale cache entries";
        }

        // Compress large entries
        $compressed = $this->compressLargeEntries();
        if ($compressed > 0) {
            $optimizations[] = "Compressed {$compressed} large cache entries";
        }

        // Adjust TTL based on usage patterns
        $ttlAdjustments = $this->optimizeTTLs();
        if (! empty($ttlAdjustments)) {
            $optimizations[] = "Optimized TTL for " . count($ttlAdjustments) . " tool methods";
        }

        return $optimizations;
    }

    /**
     * Clear all MCP cache entries
     */
    public function clearAll(): void
    {
        $pattern = self::CACHE_PREFIX . '*';
        $this->cache->forgetByPattern($pattern);
        $this->recordClearAll();
    }

    private function generateCacheKey(string $toolName, string $methodName, array $parameters): CacheKey
    {
        // Sort parameters for consistent key generation
        ksort($parameters);

        $keyString = self::CACHE_PREFIX . $toolName . ':' . $methodName . ':' . md5(serialize($parameters));

        return CacheKey::fromString($keyString);
    }

    private function generateToolTag(string $toolName): string
    {
        return 'mcp_tool_' . $toolName;
    }

    private function generateMethodPattern(string $toolName, string $methodName): string
    {
        return self::CACHE_PREFIX . $toolName . ':' . $methodName . ':*';
    }

    private function calculateTTL(string $toolName, string $methodName, CacheStrategy $strategy): int
    {
        return match ($strategy) {
            CacheStrategy::SHORT => 300,      // 5 minutes
            CacheStrategy::MEDIUM => 1800,    // 30 minutes
            CacheStrategy::LONG => 7200,      // 2 hours
            CacheStrategy::PERSISTENT => 86400, // 24 hours
            CacheStrategy::ADAPTIVE => $this->calculateAdaptiveTTL($toolName, $methodName),
            CacheStrategy::DISABLED => 0
        };
    }

    private function calculateAdaptiveTTL(string $toolName, string $methodName): int
    {
        // Base TTL on tool characteristics and usage patterns
        $baseTTL = self::DEFAULT_TTL;

        // Adjust based on tool type
        $toolMultiplier = match (true) {
            str_contains($toolName, 'Health') => 0.5,     // Health checks change frequently
            str_contains($toolName, 'Route') => 2.0,      // Routes are relatively stable
            str_contains($toolName, 'Container') => 1.5,  // Container bindings are stable
            str_contains($toolName, 'Performance') => 0.3, // Performance data changes quickly
            str_contains($toolName, 'Security') => 0.8,   // Security data should be fresh
            default => 1.0
        };

        // Adjust based on method type
        $methodMultiplier = match (true) {
            str_contains($methodName, 'health') => 0.5,
            str_contains($methodName, 'status') => 0.7,
            str_contains($methodName, 'list') => 1.5,
            str_contains($methodName, 'analyze') => 1.2,
            default => 1.0
        };

        return (int) ($baseTTL * $toolMultiplier * $methodMultiplier);
    }

    private function compressIfNeeded(mixed $data): mixed
    {
        $serialized = serialize($data);

        if (strlen($serialized) > self::COMPRESSION_THRESHOLD) {
            $compressed = gzcompress($serialized, 6);

            if ($compressed !== false && strlen($compressed) < strlen($serialized)) {
                return [
                    '_compressed' => true,
                    '_data' => base64_encode($compressed),
                    '_original_size' => strlen($serialized),
                ];
            }
        }

        return $data;
    }

    private function decompressIfNeeded(mixed $data): mixed
    {
        if (is_array($data) && isset($data['_compressed']) && $data['_compressed'] === true) {
            $compressed = base64_decode($data['_data']);
            $decompressed = gzuncompress($compressed);

            if ($decompressed !== false) {
                return unserialize($decompressed);
            }
        }

        return $data;
    }

    private function enforceMemoryLimits(): void
    {
        $currentSize = $this->calculateMemoryUsage();

        if ($currentSize > self::MAX_CACHE_SIZE) {
            // Remove least recently used entries
            $this->evictLRUEntries((int) ($currentSize * 0.2)); // Remove 20%
        }
    }

    private function calculateMemoryUsage(): int
    {
        // This would need to be implemented based on the actual cache backend
        // For now, return a placeholder
        return 0;
    }

    private function calculateHitRate(): float
    {
        $stats = method_exists($this->cache, 'getStatistics') ? $this->cache->getStatistics() : [];
        $hits = $stats['hits'] ?? 0;
        $misses = $stats['misses'] ?? 0;
        $total = $hits + $misses;

        return $total > 0 ? $hits / $total : 0.0;
    }

    private function calculateCompressionRatio(): float
    {
        // This would track compression statistics
        return 0.0;
    }

    private function calculateAverageResponseTime(): float
    {
        // This would track response times
        return 0.0;
    }

    private function getTopCachedTools(): array
    {
        // This would return the most frequently cached tools
        return [];
    }

    private function getInvalidationStats(): array
    {
        // This would return invalidation statistics
        return [];
    }

    private function removeStaleEntries(): int
    {
        // This would remove expired or stale entries
        return 0;
    }

    private function compressLargeEntries(): int
    {
        // This would compress large cache entries
        return 0;
    }

    private function optimizeTTLs(): array
    {
        // This would analyze usage patterns and optimize TTLs
        return [];
    }

    private function evictLRUEntries(int $targetBytes): void
    {
        // This would evict least recently used entries
    }

    private function recordCacheHit(string $toolName, string $methodName): void
    {
        // Record metrics for cache hits
    }

    private function recordCacheMiss(string $toolName, string $methodName): void
    {
        // Record metrics for cache misses
    }

    private function recordCacheSet(string $toolName, string $methodName, mixed $data): void
    {
        // Record metrics for cache sets
    }

    private function recordInvalidation(string $toolName, ?string $methodName = null): void
    {
        // Record metrics for cache invalidations
    }

    private function recordWarmupAttempt(string $toolName, string $methodName): void
    {
        // Record metrics for warmup attempts
    }

    private function recordClearAll(): void
    {
        // Record metrics for cache clear operations
    }
}
