<?php

declare(strict_types=1);

namespace App\Framework\Http\Parser;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Core\ValueObjects\Duration;

/**
 * High-performance caching system for parser results using Framework Cache
 * Reduces repeated parsing overhead for common patterns
 */
final readonly class ParserCache
{
    private const string CACHE_PREFIX = 'http_parser:';
    private const int MAX_CACHE_KEY_LENGTH = 200;

    private Duration $ttl;

    public function __construct(
        private Cache $cache,
        ?Duration $ttl = null
    ) {
        $this->ttl = $ttl ?? Duration::fromHours(1);
    }

    /**
     * Get cached query string parse result
     *
     * @param string $queryString
     * @return array<string, mixed>|null
     */
    public function getQueryString(string $queryString): ?array
    {
        if (! $this->shouldCache($queryString)) {
            return null;
        }

        $cacheKey = $this->generateCacheKey('query', $queryString);

        // Check if cached first, then get the value
        if ($this->cache->has($cacheKey)) {
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem->isHit && is_array($cacheItem->value)) {
                return $cacheItem->value;
            }
        }

        return null;
    }

    /**
     * Cache query string parse result
     *
     * @param string $queryString
     * @param array<string, mixed> $result
     */
    public function setQueryString(string $queryString, array $result): void
    {
        if (! $this->shouldCache($queryString)) {
            return;
        }

        $cacheKey = $this->generateCacheKey('query', $queryString);
        $this->cache->set(CacheItem::forSet($cacheKey, $result, $this->ttl));
    }

    /**
     * Get cached header parse result
     *
     * @param string $headerString
     * @return array<string, mixed>|null
     */
    public function getHeaders(string $headerString): ?array
    {
        if (! $this->shouldCache($headerString)) {
            return null;
        }

        $cacheKey = $this->generateCacheKey('headers', $headerString);

        if ($this->cache->has($cacheKey)) {
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem->isHit && is_array($cacheItem->value)) {
                return $cacheItem->value;
            }
        }

        return null;
    }

    /**
     * Cache header parse result
     *
     * @param string $headerString
     * @param array<string, mixed> $result
     */
    public function setHeaders(string $headerString, array $result): void
    {
        if (! $this->shouldCache($headerString)) {
            return;
        }

        $cacheKey = $this->generateCacheKey('headers', $headerString);
        $this->cache->set(CacheItem::forSet($cacheKey, $result, $this->ttl));
    }

    /**
     * Get cached cookie parse result
     *
     * @param string $cookieString
     * @return array<string, mixed>|null
     */
    public function getCookies(string $cookieString): ?array
    {
        if (! $this->shouldCache($cookieString)) {
            return null;
        }

        $cacheKey = $this->generateCacheKey('cookies', $cookieString);

        if ($this->cache->has($cacheKey)) {
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem->isHit && is_array($cacheItem->value)) {
                return $cacheItem->value;
            }
        }

        return null;
    }

    /**
     * Cache cookie parse result
     *
     * @param string $cookieString
     * @param array<string, mixed> $result
     */
    public function setCookies(string $cookieString, array $result): void
    {
        if (! $this->shouldCache($cookieString)) {
            return;
        }

        $cacheKey = $this->generateCacheKey('cookies', $cookieString);
        $this->cache->set(CacheItem::forSet($cacheKey, $result, $this->ttl));
    }

    /**
     * Cache parsed multipart boundary for reuse
     *
     * @param string $contentType
     * @return string|null
     */
    public function getBoundary(string $contentType): ?string
    {
        $cacheKey = $this->generateCacheKey('boundary', $contentType);

        if ($this->cache->has($cacheKey)) {
            $cacheItem = $this->cache->get($cacheKey);
            if ($cacheItem->isHit && is_string($cacheItem->value)) {
                return $cacheItem->value;
            }
        }

        return null;
    }

    /**
     * Cache multipart boundary
     *
     * @param string $contentType
     * @param string $boundary
     */
    public function setBoundary(string $contentType, string $boundary): void
    {
        $cacheKey = $this->generateCacheKey('boundary', $contentType);
        $this->cache->set(CacheItem::forSet($cacheKey, $boundary, $this->ttl));
    }

    /**
     * Clear all parser caches
     * Note: Framework Cache doesn't support pattern deletion, so this clears all cache
     */
    public function clearAll(): void
    {
        $this->cache->clear();
    }

    /**
     * Clear specific cache type
     * Note: Framework Cache doesn't support pattern deletion, so this clears all cache
     *
     * @param string $type One of: query, headers, cookies, boundary
     */
    public function clearType(string $type): void
    {
        // Since we can't delete by pattern, we clear all cache
        // This is a limitation of the current Cache interface
        $this->cache->clear();
    }

    /**
     * Get cache statistics
     *
     * @return array<string, mixed>
     */
    public function getStats(): array
    {
        // Try to get stats from cache implementation
        if (method_exists($this->cache, 'getStats')) {
            $cacheStats = $this->cache->getStats();

            // Filter for our keys if possible
            return [
                'cache_backend' => get_class($this->cache),
                'ttl' => $this->ttl->toSeconds(),
                'prefix' => self::CACHE_PREFIX,
                'backend_stats' => $cacheStats,
            ];
        }

        return [
            'cache_backend' => get_class($this->cache),
            'ttl' => $this->ttl->toSeconds(),
            'prefix' => self::CACHE_PREFIX,
        ];
    }

    /**
     * Generate cache key for input data
     */
    private function generateCacheKey(string $type, string $input): CacheKey
    {
        $baseKey = self::CACHE_PREFIX . $type . ':';

        // Use hash for long inputs to prevent cache key issues
        if (strlen($input) > self::MAX_CACHE_KEY_LENGTH) {
            return CacheKey::fromString($baseKey . 'hash_' . hash('xxh64', $input));
        }

        // Replace problematic characters for cache keys
        $cleanInput = preg_replace('/[^a-zA-Z0-9._-]/', '_', $input);

        return CacheKey::fromString($baseKey . $cleanInput);
    }

    /**
     * Check if caching should be used for given input size
     *
     * @param string $input
     * @return bool
     */
    private function shouldCache(string $input): bool
    {
        $length = strlen($input);

        // Don't cache very short strings (overhead not worth it)
        if ($length < 10) {
            return false;
        }

        // Don't cache very long strings (memory usage)
        if ($length > 4096) {
            return false;
        }

        // Don't cache if input contains sensitive data patterns
        if ($this->containsSensitiveData($input)) {
            return false;
        }

        return true;
    }

    /**
     * Check if input contains potentially sensitive data that shouldn't be cached
     *
     * @param string $input
     * @return bool
     */
    private function containsSensitiveData(string $input): bool
    {
        $sensitivePatterns = [
            '/password/i',
            '/token/i',
            '/secret/i',
            '/key/i',
            '/auth/i',
            '/session/i',
        ];

        return array_any($sensitivePatterns, fn ($pattern) => preg_match($pattern, $input));

    }
}
