<?php

declare(strict_types=1);

namespace App\Framework\LiveComponents\Cache;

use App\Framework\Core\ValueObjects\Percentage;

/**
 * Cache Metrics Value Object
 *
 * Tracks performance metrics for LiveComponent caching system.
 *
 * Metrics Tracked:
 * - Hit/Miss rates
 * - Average lookup times
 * - Cache size statistics
 * - Invalidation counts
 *
 * Performance Targets:
 * - State Cache: ~70% faster initialization
 * - Slot Cache: ~60% faster resolution
 * - Template Cache: ~80% faster rendering
 */
final readonly class CacheMetrics
{
    public function __construct(
        public CacheType $cacheType,
        public int $hits = 0,
        public int $misses = 0,
        public float $averageLookupTimeMs = 0.0,
        public int $totalSize = 0,          // Number of cached items
        public int $invalidations = 0,
        public int $evictions = 0,
        public Percentage $hitRate = new Percentage(0.0),
        public Percentage $missRate = new Percentage(0.0)
    ) {
    }

    /**
     * Create empty metrics for cache type
     */
    public static function empty(CacheType $cacheType): self
    {
        return new self(
            cacheType: $cacheType,
            hitRate: Percentage::zero(),
            missRate: Percentage::zero()
        );
    }

    /**
     * Record cache hit
     */
    public function withHit(float $lookupTimeMs): self
    {
        $newHits = $this->hits + 1;
        $newTotal = $newHits + $this->misses;

        return new self(
            cacheType: $this->cacheType,
            hits: $newHits,
            misses: $this->misses,
            averageLookupTimeMs: $this->calculateNewAverage($lookupTimeMs),
            totalSize: $this->totalSize,
            invalidations: $this->invalidations,
            evictions: $this->evictions,
            hitRate: $newTotal > 0 ? Percentage::fromRatio($newHits, $newTotal) : Percentage::zero(),
            missRate: $newTotal > 0 ? Percentage::fromRatio($this->misses, $newTotal) : Percentage::zero()
        );
    }

    /**
     * Record cache miss
     */
    public function withMiss(float $lookupTimeMs): self
    {
        $newMisses = $this->misses + 1;
        $newTotal = $this->hits + $newMisses;

        return new self(
            cacheType: $this->cacheType,
            hits: $this->hits,
            misses: $newMisses,
            averageLookupTimeMs: $this->calculateNewAverage($lookupTimeMs),
            totalSize: $this->totalSize,
            invalidations: $this->invalidations,
            evictions: $this->evictions,
            hitRate: $newTotal > 0 ? Percentage::fromRatio($this->hits, $newTotal) : Percentage::zero(),
            missRate: $newTotal > 0 ? Percentage::fromRatio($newMisses, $newTotal) : Percentage::zero()
        );
    }

    /**
     * Record cache invalidation
     */
    public function withInvalidation(): self
    {
        return new self(
            cacheType: $this->cacheType,
            hits: $this->hits,
            misses: $this->misses,
            averageLookupTimeMs: $this->averageLookupTimeMs,
            totalSize: $this->totalSize,
            invalidations: $this->invalidations + 1,
            evictions: $this->evictions,
            hitRate: $this->hitRate,
            missRate: $this->missRate
        );
    }

    /**
     * Update cache size
     */
    public function withSize(int $size): self
    {
        return new self(
            cacheType: $this->cacheType,
            hits: $this->hits,
            misses: $this->misses,
            averageLookupTimeMs: $this->averageLookupTimeMs,
            totalSize: $size,
            invalidations: $this->invalidations,
            evictions: $this->evictions,
            hitRate: $this->hitRate,
            missRate: $this->missRate
        );
    }

    /**
     * Calculate new average lookup time
     */
    private function calculateNewAverage(float $newLookupTime): float
    {
        $totalOperations = $this->hits + $this->misses;

        if ($totalOperations === 0) {
            return $newLookupTime;
        }

        // Weighted average
        return (($this->averageLookupTimeMs * $totalOperations) + $newLookupTime) / ($totalOperations + 1);
    }

    /**
     * Get total operations (hits + misses)
     */
    public function getTotalOperations(): int
    {
        return $this->hits + $this->misses;
    }

    /**
     * Check if hit rate meets performance target
     */
    public function meetsPerformanceTarget(Percentage $targetHitRate): bool
    {
        return $this->hitRate->greaterThanOrEqual($targetHitRate);
    }

    /**
     * Get performance grade based on hit rate
     */
    public function getPerformanceGrade(): string
    {
        $rate = $this->hitRate->getValue();

        return match (true) {
            $rate >= 90.0 => 'A',
            $rate >= 80.0 => 'B',
            $rate >= 70.0 => 'C',
            $rate >= 60.0 => 'D',
            default => 'F'
        };
    }

    /**
     * Convert to array for reporting
     */
    public function toArray(): array
    {
        return [
            'cache_type' => $this->cacheType->value,
            'hits' => $this->hits,
            'misses' => $this->misses,
            'hit_rate' => $this->hitRate->format(2),
            'miss_rate' => $this->missRate->format(2),
            'average_lookup_time_ms' => round($this->averageLookupTimeMs, 3),
            'total_size' => $this->totalSize,
            'invalidations' => $this->invalidations,
            'evictions' => $this->evictions,
            'total_operations' => $this->getTotalOperations(),
            'performance_grade' => $this->getPerformanceGrade(),
        ];
    }

    /**
     * Merge metrics from multiple sources
     */
    public static function merge(self ...$metrics): self
    {
        if (empty($metrics)) {
            return self::empty(CacheType::MERGED);
        }

        $totalHits = 0;
        $totalMisses = 0;
        $totalInvalidations = 0;
        $totalEvictions = 0;
        $totalSize = 0;
        $totalLookupTime = 0.0;

        foreach ($metrics as $metric) {
            $totalHits += $metric->hits;
            $totalMisses += $metric->misses;
            $totalInvalidations += $metric->invalidations;
            $totalEvictions += $metric->evictions;
            $totalSize += $metric->totalSize;
            $totalLookupTime += $metric->averageLookupTimeMs * ($metric->hits + $metric->misses);
        }

        $totalOperations = $totalHits + $totalMisses;
        $averageLookupTime = $totalOperations > 0 ? $totalLookupTime / $totalOperations : 0.0;

        return new self(
            cacheType: CacheType::MERGED,
            hits: $totalHits,
            misses: $totalMisses,
            averageLookupTimeMs: $averageLookupTime,
            totalSize: $totalSize,
            invalidations: $totalInvalidations,
            evictions: $totalEvictions,
            hitRate: $totalOperations > 0 ? Percentage::fromRatio($totalHits, $totalOperations) : Percentage::zero(),
            missRate: $totalOperations > 0 ? Percentage::fromRatio($totalMisses, $totalOperations) : Percentage::zero()
        );
    }
}
