<?php

declare(strict_types=1);

namespace App\Framework\Cache\Metrics;

use App\Framework\Cache\Cache;
use App\Framework\Cache\CacheIdentifier;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CacheResult;
use App\Framework\Core\ValueObjects\Duration;
use App\Framework\Performance\Contracts\PerformanceCollectorInterface;
use App\Framework\Performance\PerformanceCategory;

final readonly class MetricsDecoratedCache implements Cache
{
    public function __construct(
        private Cache $inner,
        private CacheMetricsInterface $metrics,
        private string $driverName,
        private ?PerformanceCollectorInterface $performanceCollector = null,
        private bool $performanceEnabled = true
    ) {
    }

    public function get(CacheIdentifier ...$identifiers): CacheResult
    {
        if (empty($identifiers)) {
            return CacheResult::empty();
        }

        // Performance tracking setup
        $metricKey = 'cache_get_batch';
        $identifierCount = count($identifiers);
        $context = [
            'operation' => 'get_batch',
            'driver' => $this->driverName,
            'identifier_count' => $identifierCount,
            'identifiers' => array_map(fn ($id) => $this->sanitizeKey($id->toString()), $identifiers),
        ];

        if ($this->performanceCollector && $this->performanceEnabled) {
            $this->performanceCollector->startTiming($metricKey, PerformanceCategory::CACHE, $context);
        }

        try {
            $startTime = microtime(true);
            $result = $this->inner->get(...$identifiers);
            $latency = microtime(true) - $startTime;

            // Record metrics for each item
            foreach ($result->getItems() as $item) {
                $keyString = $item->key->toString();
                $size = 0;

                if ($item->isHit) {
                    $size = $this->calculateSize($item->value);
                    $this->metrics->recordHit($this->driverName, $keyString, $size, $latency);
                } else {
                    $this->metrics->recordMiss($this->driverName, $keyString, $latency);
                }
            }

            // Record batch operation metrics
            $hitCount = $result->getHits()->count();
            $missCount = $result->getMisses()->count();
            $hitRatio = $result->getHitRatio();

            // Record batch operation metrics (if available)
            if (method_exists($this->metrics, 'recordBatchOperation')) {
                $this->metrics->recordBatchOperation(
                    $this->driverName,
                    'get',
                    $identifierCount,
                    $hitCount,
                    $missCount,
                    $hitRatio,
                    $latency
                );
            }

            // Performance tracking for batch operation
            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->increment('cache_hits', PerformanceCategory::CACHE, $hitCount, $context);
                $this->performanceCollector->increment('cache_misses', PerformanceCategory::CACHE, $missCount, $context);
                $this->performanceCollector->recordMetric('cache_hit_rate', PerformanceCategory::CACHE, $hitRatio, $context);
                $this->performanceCollector->increment('cache_operations_total', PerformanceCategory::CACHE, 1, $context);
            }

            return $result;
        } catch (\Throwable $e) {
            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->increment(
                    'cache_errors',
                    PerformanceCategory::CACHE,
                    1,
                    array_merge($context, ['error' => $e::class])
                );
            }

            throw $e;
        } finally {
            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->endTiming($metricKey);
            }
        }
    }

    public function set(CacheItem ...$items): bool
    {
        if (empty($items)) {
            return true;
        }

        $metricKey = 'cache_set_batch';
        $itemCount = count($items);
        $totalSize = array_sum(array_map(fn ($item) => $this->calculateSize($item->value), $items));

        $context = [
            'operation' => 'set_batch',
            'driver' => $this->driverName,
            'item_count' => $itemCount,
            'total_size' => $totalSize,
        ];

        if ($this->performanceCollector && $this->performanceEnabled) {
            $this->performanceCollector->startTiming($metricKey, PerformanceCategory::CACHE, $context);
        }

        try {
            $startTime = microtime(true);
            $result = $this->inner->set(...$items);
            $latency = microtime(true) - $startTime;

            // Record metrics for each item
            foreach ($items as $item) {
                $keyString = (string)$item->key;
                $valueSize = $this->calculateSize($item->value);
                $ttlSeconds = $item->ttl !== null ? $item->ttl->toCacheSeconds() : 0;

                if ($result) {
                    $this->metrics->recordSet($this->driverName, $keyString, $valueSize, $ttlSeconds, $latency);
                }
            }

            if ($result) {
                if ($this->performanceCollector && $this->performanceEnabled) {
                    $this->performanceCollector->increment('cache_sets_success', PerformanceCategory::CACHE, $itemCount, $context);
                    $this->performanceCollector->recordMetric('cache_stored_bytes', PerformanceCategory::CACHE, $totalSize, $context);
                }
            } else {
                if ($this->performanceCollector && $this->performanceEnabled) {
                    $this->performanceCollector->increment('cache_sets_failed', PerformanceCategory::CACHE, $itemCount, $context);
                }
            }

            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->increment('cache_operations_total', PerformanceCategory::CACHE, 1, $context);
            }

            return $result;
        } catch (\Throwable $e) {
            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->increment(
                    'cache_errors',
                    PerformanceCategory::CACHE,
                    1,
                    array_merge($context, ['error' => $e::class])
                );
            }

            throw $e;
        } finally {
            if ($this->performanceCollector && $this->performanceEnabled) {
                $this->performanceCollector->endTiming($metricKey);
            }
        }
    }

    public function has(CacheIdentifier ...$identifiers): array
    {
        // Note: 'has' doesn't trigger hit/miss metrics as it's not a data access
        return $this->inner->has(...$identifiers);
    }

    public function forget(CacheIdentifier ...$identifiers): bool
    {
        if (empty($identifiers)) {
            return true;
        }

        $startTime = microtime(true);
        $result = $this->inner->forget(...$identifiers);
        $latency = microtime(true) - $startTime;

        if ($result) {
            foreach ($identifiers as $identifier) {
                $keyString = $identifier->toString();
                $this->metrics->recordDelete($this->driverName, $keyString, $latency);
            }
        }

        return $result;
    }

    public function delete(CacheKey $key): bool
    {
        // Alias for forget() - some cache implementations use delete()
        return $this->forget($key);
    }

    public function clear(): bool
    {
        $startTime = microtime(true);
        $result = $this->inner->clear();
        $latency = microtime(true) - $startTime;

        if ($result) {
            $this->metrics->recordClear($this->driverName, $latency);
        }

        return $result;
    }

    public function remember(CacheKey $key, callable $callback, ?Duration $ttl = null): CacheItem
    {
        // For remember, we track both the get attempt and potential set
        $startTime = microtime(true);
        $item = $this->inner->remember($key, $callback, $ttl);
        $totalLatency = microtime(true) - $startTime;
        $keyString = (string)$key;
        $ttlSeconds = $ttl !== null ? $ttl->toCacheSeconds() : 3600; // Default to 1 hour if not specified

        if ($item->isHit) {
            // Cache hit - only record the get operation
            $size = $this->calculateSize($item->value);
            $this->metrics->recordHit($this->driverName, $keyString, $size, $totalLatency);
        } else {
            // Cache miss + set - record both operations
            $size = $this->calculateSize($item->value);

            // Split latency between miss and set (rough estimate)
            $missLatency = $totalLatency * 0.1; // Assume miss is 10% of total
            $setLatency = $totalLatency * 0.9;   // Assume set is 90% of total

            $this->metrics->recordMiss($this->driverName, $keyString, $missLatency);
            $this->metrics->recordSet($this->driverName, $keyString, $size, $ttlSeconds, $setLatency);
        }

        return $item;
    }

    /**
     * Get the underlying cache instance (for advanced usage)
     */
    public function getInner(): Cache
    {
        return $this->inner;
    }

    /**
     * Get the metrics collector
     */
    public function getMetrics(): CacheMetricsInterface
    {
        return $this->metrics;
    }

    /**
     * Get the driver name
     */
    public function getDriverName(): string
    {
        return $this->driverName;
    }

    /**
     * Calculate approximate size of a value in bytes
     */
    private function calculateSize(mixed $value): int
    {
        if ($value === null) {
            return 0;
        }

        try {
            // Use serialization to get approximate size
            return strlen(serialize($value));
        } catch (\Throwable $e) {
            // Fallback for unserializable values
            if (is_string($value)) {
                return strlen($value);
            }
            if (is_array($value)) {
                return strlen(json_encode($value));
            }
            if (is_object($value)) {
                return strlen(json_encode($value));
            }

            // Rough estimate for primitives
            return match (gettype($value)) {
                'boolean' => 1,
                'integer' => 8,
                'double' => 8,
                default => 16, // Fallback
            };
        }
    }

    /**
     * Sanitizes cache keys for safe logging (removes potential sensitive data)
     */
    private function sanitizeKey(string $key): string
    {
        if (strlen($key) > 100) {
            return substr($key, 0, 100) . '...';
        }

        // Remove potential sensitive patterns
        $key = preg_replace('/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/', '[EMAIL]', $key);
        $key = preg_replace('/\b\d{4}[-\s]\d{4}[-\s]\d{4}[-\s]\d{4}\b/', '[CARD]', $key);

        return $key;
    }
}
