<?php

declare(strict_types=1);

namespace App\Framework\Cache\Metrics;

final class CacheMetrics implements CacheMetricsInterface
{
    private array $stats = [
        'hits' => 0,
        'misses' => 0,
        'sets' => 0,
        'deletes' => 0,
        'clears' => 0,
        'total_size' => 0,
        'total_latency' => 0.0,
        'start_time' => 0,
        'drivers' => [],
    ];

    private array $keyStats = [];

    private int $maxTrackedKeys = 1000; // Prevent memory issues

    private string $persistenceFile;

    private float $samplingRate = 0.1; // Only record 10% of operations to avoid huge numbers

    private bool $debugMode = false;

    private int $lastSaveTime = 0;

    private int $saveInterval = 30; // Save every 30 seconds

    public function __construct(string $persistenceFile = '/tmp/cache_metrics.json')
    {
        $this->persistenceFile = $persistenceFile;
        $this->loadFromFile();
        if ($this->stats['start_time'] === 0) {
            $this->stats['start_time'] = time();
        }
        $this->lastSaveTime = time();
    }

    public function __destruct()
    {
        // Save metrics on shutdown
        $this->saveToFile();
    }

    public function enableDebugMode(): void
    {
        $this->debugMode = true;
        $this->samplingRate = 1.0; // Record everything in debug mode
    }

    private function shouldRecord(): bool
    {
        return $this->debugMode || (mt_rand() / mt_getrandmax()) < $this->samplingRate;
    }

    public function recordHit(string $driver, string $key, int $size = 0, float $latency = 0.0): void
    {
        if (! $this->shouldRecord()) {
            return;
        }

        $this->stats['hits']++;
        $this->stats['total_latency'] += $latency;

        $this->initDriver($driver);
        $this->stats['drivers'][$driver]['hits']++;
        $this->stats['drivers'][$driver]['total_latency'] += $latency;

        $this->trackKey($key, 'hit', $size);
        $this->maybeSave();
    }

    public function recordMiss(string $driver, string $key, float $latency = 0.0): void
    {
        if (! $this->shouldRecord()) {
            return;
        }

        $this->stats['misses']++;
        $this->stats['total_latency'] += $latency;

        $this->initDriver($driver);
        $this->stats['drivers'][$driver]['misses']++;
        $this->stats['drivers'][$driver]['total_latency'] += $latency;

        $this->trackKey($key, 'miss', 0);
        $this->maybeSave();
    }

    public function recordSet(string $driver, string $key, int $size, int $ttl, float $latency = 0.0): void
    {
        if (! $this->shouldRecord()) {
            return;
        }

        $this->stats['sets']++;
        $this->stats['total_size'] += $size;
        $this->stats['total_latency'] += $latency;

        $this->initDriver($driver);
        $this->stats['drivers'][$driver]['sets']++;
        $this->stats['drivers'][$driver]['total_size'] += $size;
        $this->stats['drivers'][$driver]['total_latency'] += $latency;

        $this->trackKey($key, 'set', $size, $ttl);
        $this->maybeSave();
    }

    public function recordDelete(string $driver, string $key, float $latency = 0.0): void
    {
        if (! $this->shouldRecord()) {
            return;
        }

        $this->stats['deletes']++;
        $this->stats['total_latency'] += $latency;

        $this->initDriver($driver);
        $this->stats['drivers'][$driver]['deletes']++;
        $this->stats['drivers'][$driver]['total_latency'] += $latency;

        $this->trackKey($key, 'delete', 0);
        $this->maybeSave();
    }

    public function recordClear(string $driver, float $latency = 0.0): void
    {
        // Always record clear operations (they are rare and important)
        $this->stats['clears']++;
        $this->stats['total_latency'] += $latency;

        $this->initDriver($driver);
        $this->stats['drivers'][$driver]['clears']++;
        $this->stats['drivers'][$driver]['total_latency'] += $latency;

        // Clear key tracking for this driver (simplified - clear all since we don't track driver per key)
        $this->keyStats = [];
        // Save immediately for clear operations as they are rare but important
        $this->saveToFile();
    }

    public function getStats(): CacheStatsSnapshot
    {
        $hitRate = $this->getHitRate();
        $avgLatency = $this->getAverageLatency();
        $driverStats = $this->getFormattedDriverStats();
        $topKeys = $this->getTopKeys();
        $heaviestKeys = $this->getHeaviestKeys();

        return new CacheStatsSnapshot(
            hitRate: $hitRate,
            totalHits: $this->stats['hits'],
            totalMisses: $this->stats['misses'],
            totalSets: $this->stats['sets'],
            totalDeletes: $this->stats['deletes'],
            totalClears: $this->stats['clears'],
            driverStats: $driverStats,
            topKeys: $topKeys,
            heaviestKeys: $heaviestKeys,
            avgLatency: $avgLatency,
            totalSize: $this->stats['total_size'],
            timestamp: $this->stats['start_time']
        );
    }

    public function getStatsForDriver(string $driver): array
    {
        if (! isset($this->stats['drivers'][$driver])) {
            return [];
        }

        $driverData = $this->stats['drivers'][$driver];
        $totalOps = $driverData['hits'] + $driverData['misses'];
        $hitRate = $totalOps > 0 ? $driverData['hits'] / $totalOps : 0.0;
        $avgLatency = $totalOps > 0 ? $driverData['total_latency'] / $totalOps : 0.0;

        return [
            'driver' => $driver,
            'hit_rate' => $hitRate,
            'hits' => $driverData['hits'],
            'misses' => $driverData['misses'],
            'sets' => $driverData['sets'],
            'deletes' => $driverData['deletes'],
            'clears' => $driverData['clears'],
            'total_size' => $driverData['total_size'],
            'avg_latency' => $avgLatency,
        ];
    }

    public function reset(): void
    {
        $this->stats = [
            'hits' => 0,
            'misses' => 0,
            'sets' => 0,
            'deletes' => 0,
            'clears' => 0,
            'total_size' => 0,
            'total_latency' => 0.0,
            'start_time' => time(),
            'drivers' => [],
        ];
        $this->keyStats = [];
        $this->saveToFile();
    }

    private function loadFromFile(): void
    {
        if (! file_exists($this->persistenceFile)) {
            return;
        }

        try {
            $data = json_decode(file_get_contents($this->persistenceFile), true);
            if (is_array($data)) {
                $this->stats = array_merge($this->stats, $data['stats'] ?? []);
                $this->keyStats = $data['keyStats'] ?? [];
            }
        } catch (\Throwable $e) {
            // If loading fails, start fresh
            error_log("Failed to load cache metrics: " . $e->getMessage());
        }
    }

    private function maybeSave(): void
    {
        $now = time();
        if ($now - $this->lastSaveTime >= $this->saveInterval) {
            $this->saveToFile();
            $this->lastSaveTime = $now;
        }
    }

    private function saveToFile(): void
    {
        try {
            $data = [
                'stats' => $this->stats,
                'keyStats' => $this->keyStats,
                'saved_at' => time(),
            ];
            file_put_contents($this->persistenceFile, json_encode($data, JSON_PRETTY_PRINT));
        } catch (\Throwable $e) {
            // If saving fails, continue silently
            error_log("Failed to save cache metrics: " . $e->getMessage());
        }
    }

    public function getHitRate(): float
    {
        $total = $this->stats['hits'] + $this->stats['misses'];

        return $total > 0 ? $this->stats['hits'] / $total : 0.0;
    }

    public function getTopKeys(int $limit = 10): array
    {
        // Sort by access frequency
        $sorted = $this->keyStats;
        uasort($sorted, fn ($a, $b) => $b['access_count'] <=> $a['access_count']);

        return array_slice($sorted, 0, $limit, true);
    }

    public function getHeaviestKeys(int $limit = 10): array
    {
        // Sort by size
        $sorted = $this->keyStats;
        uasort($sorted, fn ($a, $b) => $b['size'] <=> $a['size']);

        return array_slice($sorted, 0, $limit, true);
    }

    private function initDriver(string $driver): void
    {
        if (! isset($this->stats['drivers'][$driver])) {
            $this->stats['drivers'][$driver] = [
                'hits' => 0,
                'misses' => 0,
                'sets' => 0,
                'deletes' => 0,
                'clears' => 0,
                'total_size' => 0,
                'total_latency' => 0.0,
            ];
        }
    }

    private function trackKey(string $key, string $operation, int $size, int $ttl = 0): void
    {
        // Prevent memory issues by limiting tracked keys
        if (count($this->keyStats) >= $this->maxTrackedKeys && ! isset($this->keyStats[$key])) {
            return;
        }

        if (! isset($this->keyStats[$key])) {
            $this->keyStats[$key] = [
                'access_count' => 0,
                'hit_count' => 0,
                'miss_count' => 0,
                'size' => 0,
                'last_access' => time(),
                'ttl' => 0,
            ];
        }

        $this->keyStats[$key]['access_count']++;
        $this->keyStats[$key]['last_access'] = time();

        switch ($operation) {
            case 'hit':
                $this->keyStats[$key]['hit_count']++;

                break;
            case 'miss':
                $this->keyStats[$key]['miss_count']++;

                break;
            case 'set':
                $this->keyStats[$key]['size'] = $size;
                $this->keyStats[$key]['ttl'] = $ttl;

                break;
            case 'delete':
                unset($this->keyStats[$key]);

                break;
        }
    }

    private function getAverageLatency(): float
    {
        $totalOps = $this->stats['hits'] + $this->stats['misses'] + $this->stats['sets'] + $this->stats['deletes'];

        return $totalOps > 0 ? $this->stats['total_latency'] / $totalOps : 0.0;
    }

    private function getFormattedDriverStats(): array
    {
        $formatted = [];
        foreach ($this->stats['drivers'] as $driver => $stats) {
            $formatted[$driver] = $this->getStatsForDriver($driver);
        }

        return $formatted;
    }
}
