<?php

namespace FleekDash\Services;

use Exception;

class CacheOptimizer
{
    private AnalyticsCacheService $cacheService;
    private PerformanceAuditService $performanceService;
    private bool $compressionEnabled = true;
    private array $cacheLayers = [];

    const COMPRESSION_THRESHOLD = 1024; // 1KB
    const CACHE_LAYERS = [
        'memory' => ['ttl' => 60, 'priority' => 1],      // 1 minute (hot)
        'fast' => ['ttl' => 300, 'priority' => 2],       // 5 minutes
        'normal' => ['ttl' => 1800, 'priority' => 3],    // 30 minutes
        'slow' => ['ttl' => 7200, 'priority' => 4]       // 2 hours (cold)
    ];

    // Per-domain cache policies
    const CACHE_STRATEGIES = [
        'analytics' => [
            'layers' => ['memory', 'fast'],
            'compression' => true,
            'invalidation' => 'time_based',
            'ttl' => 300
        ],
        'stats' => [
            'layers' => ['fast', 'normal'],
            'compression' => true,
            'invalidation' => 'event_based',
            'ttl' => 1800
        ],
        'growth_meter' => [
            'layers' => ['normal', 'slow'],
            'compression' => false,
            'invalidation' => 'manual',
            'ttl' => 3600
        ],
        'real_time' => [
            'layers' => ['memory'],
            'compression' => false,
            'invalidation' => 'time_based',
            'ttl' => 10
        ]
    ];

    public function __construct()
    {
        $this->cacheService = new AnalyticsCacheService();
        $this->performanceService = new PerformanceAuditService();
        $this->initializeCacheLayers();
    }

    /**
     * Prime in-memory layer stats for observability.
     */
    private function initializeCacheLayers()
    {
        foreach (self::CACHE_LAYERS as $layer => $config) {
            $this->cacheLayers[$layer] = [
                'ttl' => $config['ttl'],
                'priority' => $config['priority'],
                'hits' => 0,
                'misses' => 0,
                'size' => 0
            ];
        }
    }

    /**
     * Write payload through the configured layer stack.
     */
    public function optimizeCache(string $key, mixed $data, string $data_type = 'normal', array $params = []): array
    {
        unset($params);
        $strategy = $this->getCacheStrategy($data_type);
        $start_time = microtime(true);

        // Compress when over threshold
        if ($strategy['compression'] && strlen(serialize($data)) > self::COMPRESSION_THRESHOLD) {
            $data = $this->compressData($data);
            $key = "compressed_{$key}";
        }

        // Fan-out to every layer in the strategy
        $cache_result = $this->cacheInLayers($key, $data, $strategy);

        $execution_time = microtime(true) - $start_time;
        $this->performanceService->recordCacheOperation($key, $execution_time, 'set');

        return $cache_result;
    }

    /**
     * Resolve strategy definition (defaults to generic normal profile).
     */
    private function getCacheStrategy(string $data_type): array
    {
        return self::CACHE_STRATEGIES[$data_type] ?? self::CACHE_STRATEGIES['normal'];
    }

    /**
     * Materialize the value in each cache tier.
     */
    private function cacheInLayers(string $key, mixed $data, array $strategy): array
    {
        $results = [];

        foreach ($strategy['layers'] as $layer) {
            $layer_config = self::CACHE_LAYERS[$layer];
            $cache_key = "{$layer}_{$key}";

            $result = $this->cacheService->set($cache_key, $data, $layer_config['ttl']);

            if ($result) {
                $this->cacheLayers[$layer]['hits']++;
                $this->cacheLayers[$layer]['size'] += strlen(serialize($data));
                $results[$layer] = true;
            } else {
                $this->cacheLayers[$layer]['misses']++;
                $results[$layer] = false;
            }
        }

        return $results;
    }

    /**
     * Read from the fastest layer that has the key.
     */
    public function getOptimizedCache(string $key, string $data_type = 'normal'): mixed
    {
        $strategy = $this->getCacheStrategy($data_type);
        $start_time = microtime(true);

        // Walk layers by priority until a hit
        foreach ($strategy['layers'] as $layer) {
            $cache_key = "{$layer}_{$key}";
            $data = $this->cacheService->get($cache_key);

            if ($data !== false) {
                // Transparently inflate compressed entries
                if (strpos($key, 'compressed_') === 0) {
                    $data = $this->decompressData($data);
                }

                $execution_time = microtime(true) - $start_time;
                $this->performanceService->recordCacheOperation($cache_key, $execution_time, 'get');
                $this->cacheLayers[$layer]['hits']++;

                return $data;
            } else {
                $this->cacheLayers[$layer]['misses']++;
            }
        }

        return false;
    }

    /**
     * gzip-compress serialized payloads above threshold.
     */
    private function compressData(mixed $data): mixed
    {
        if (!$this->compressionEnabled) {
            return $data;
        }

        $serialized = serialize($data);

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

        return $data;
    }

    /**
     * Decompress stored cache payload.
     */
    private function decompressData(mixed $data): mixed
    {
        $result = $data;
        if (!$this->compressionEnabled) {
            return $result;
        }

        if (is_string($data) && base64_decode($data, true) !== false) {
            try {
                $decoded = base64_decode($data);
                $decompressed = gzuncompress($decoded);
                $result = unserialize($decompressed);
            } catch (Exception $e) {
                // Return opaque payload if inflate fails
                $result = $data;
            }
        }

        return $result;
    }

    /**
     * Smart cache invalidation by strategy.
     */
    public function invalidateCache(string $pattern, string $strategy = 'pattern'): int
    {
        $invalidated = 0;

        switch ($strategy) {
            case 'pattern':
                $invalidated = $this->invalidateByPattern($pattern);
                break;
            case 'time_based':
                $invalidated = $this->invalidateTimeBased($pattern);
                break;
            case 'event_based':
                $invalidated = $this->invalidateEventBased($pattern);
                break;
            case 'manual':
                $invalidated = $this->invalidateManual($pattern);
                break;
            default:
                $invalidated = $this->invalidateByPattern($pattern);
                break;
        }

        return $invalidated;
    }

    /**
     * Invalidate keys matching a pattern across layers.
     */
    private function invalidateByPattern(string $pattern): int
    {
        $invalidated = 0;

        foreach (array_keys(self::CACHE_LAYERS) as $layer) {
            $cache_key = "{$layer}_{$pattern}";
            if ($this->cacheService->delete($cache_key)) {
                $invalidated++;
            }
        }

        return $invalidated;
    }

    /**
     * Time-based invalidation (TTL expiry).
     */
    private function invalidateTimeBased(string $pattern): int
    {
        // Drop entries past TTL for the pattern
        $invalidated = 0;
        $current_time = time();

        foreach (self::CACHE_LAYERS as $layer => $config) {
            $cache_key = "{$layer}_{$pattern}";
            $cached_data = $this->cacheService->get($cache_key);

            if ($cached_data !== false) {
                // Delete when past layer TTL
                $metadata = $this->cacheService->get_metadata($cache_key);
                if ($metadata && ($current_time - $metadata['timestamp']) > $config['ttl']) {
                    $this->cacheService->delete($cache_key);
                    $invalidated++;
                }
            }
        }

        return $invalidated;
    }

    /**
     * Event-driven invalidation using known pseudo-event names.
     */
    private function invalidateEventBased(string $pattern): int
    {
        // Invalidate when known event markers appear in the pattern
        $events = [
            'post_created' => ['analytics', 'stats'],
            'user_registered' => ['analytics', 'growth_meter'],
            'comment_added' => ['stats'],
            'page_viewed' => ['analytics', 'real_time']
        ];

        $invalidated = 0;

        foreach ($events as $event => $affected_types) {
            if (strpos($pattern, $event) !== false) {
                foreach ($affected_types as $type) {
                    $invalidated += $this->invalidateByPattern("{$type}_{$pattern}");
                }
            }
        }

        return $invalidated;
    }

    /**
     * Manual invalidation (same as pattern pass-through).
     */
    private function invalidateManual(string $pattern): int
    {
        // Force flush for the pattern
        return $this->invalidateByPattern($pattern);
    }

    /**
     * Suggest optimizations from per-layer usage stats.
     */
    public function optimizeCacheUsage(): array
    {
        $optimizations = [];

        // Per-layer hit rate analysis
        foreach ($this->cacheLayers as $layer => $stats) {
            $total_requests = $stats['hits'] + $stats['misses'];
            $hit_rate = $total_requests > 0 ? ($stats['hits'] / $total_requests) * 100 : 0;

            if ($hit_rate < 50) {
                $optimizations[] = [
                    'layer' => $layer,
                    'issue' => 'Low hit rate',
                    'hit_rate' => round($hit_rate, 2),
                    'recommendation' => 'Consider reducing TTL or adjusting strategy'
                ];
            }

            // Flag oversized layer payloads
            if ($stats['size'] > 10 * 1024 * 1024) { // 10MB
                $optimizations[] = [
                    'layer' => $layer,
                    'issue' => 'Large cache size',
                    'size_mb' => round($stats['size'] / 1024 / 1024, 2),
                    'recommendation' => 'Consider cleanup or compression'
                ];
            }
        }

        return $optimizations;
    }

    /**
     * Auto cleanup when layers exceed size budget.
     */
    public function autoCleanupCache(): array
    {
        $cleaned = 0;
        $total_size = 0;

        foreach (array_keys(self::CACHE_LAYERS) as $layer) {
            $layer_stats = $this->cacheLayers[$layer];

            // Clean when layer footprint is large
            if ($layer_stats['size'] > 5 * 1024 * 1024) { // 5MB
                $cleaned += $this->cleanupLayer($layer);
                $total_size += $layer_stats['size'];
            }
        }

        return [
            'cleaned_entries' => $cleaned,
            'freed_size_mb' => round($total_size / 1024 / 1024, 2)
        ];
    }

    /**
     * Clear a single cache layer (placeholder).
     */
    private function cleanupLayer(string $layer): int
    {
        $layerKey = (string) $layer;
        if ($layerKey === '') {
            return 0;
        }

        // Layer-specific purge not implemented; stub returns zero removed entries
        return 0;
    }

    /**
     * Return aggregated optimized-cache statistics.
     */
    public function getCacheStats(): array
    {
        $stats = [
            'layers' => $this->cacheLayers,
            'total_hits' => 0,
            'total_misses' => 0,
            'total_size_mb' => 0,
            'compression_enabled' => $this->compressionEnabled
        ];

        foreach ($this->cacheLayers as $layer_stats) {
            $stats['total_hits'] += $layer_stats['hits'];
            $stats['total_misses'] += $layer_stats['misses'];
            $stats['total_size_mb'] += $layer_stats['size'] / 1024 / 1024;
        }

        $total_requests = $stats['total_hits'] + $stats['total_misses'];
        $stats['overall_hit_rate'] = $total_requests > 0 ?
            round(($stats['total_hits'] / $total_requests) * 100, 2) : 0;

        return $stats;
    }

    /**
     * Apply runtime cache optimization flags (e.g. compression).
     */
    public function configureCacheOptimizations(array $config): bool
    {
        if (isset($config['compression_enabled'])) {
            $this->compressionEnabled = $config['compression_enabled'];
        }

        // Note: CACHE_LAYERS is a constant and cannot be modified
        // Layer configurations are handled through the cache_layers property

        return true;
    }
}
