<?php

namespace App\Framework\Cache;

final readonly class MultiLevelCache implements Cache
{
    private const int MAX_FAST_CACHE_SIZE = 1024; //1KB
    private const int FAST_CACHE_TTL = 300;
    public function __construct(
        private Cache $fastCache,   // z.B. ArrayCache
        private Cache $slowCache    // z.B. RedisCache, FileCache
    ) {}

    public function get(string $key): CacheItem
    {
        $item = $this->fastCache->get($key);
        if ($item->isHit) {
            return $item;
        }

        // Fallback auf "langsame" Ebene
        $item = $this->slowCache->get($key);
        if ($item->isHit) {
            // In schnellen Cache zurücklegen (optional mit TTL aus Slow-Item)
            if($this->shouldCacheInFast($item->value)) {
                $this->fastCache->set($key, $item->value, self::FAST_CACHE_TTL);
            }
        }
        return $item;
    }

    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        $slowSuccess = $this->slowCache->set($key, $value, $ttl);

        $fastSuccess = true;
        if($this->shouldCacheInFast($value)) {
            $fastTtl = min($ttl ?? self::FAST_CACHE_TTL, self::FAST_CACHE_TTL);
            $fastSuccess = $this->fastCache->set($key, $value, $fastTtl);
        }

        return $slowSuccess && $fastSuccess;
    }

    public function forget(string $key): bool
    {
        $s1 = $this->fastCache->forget($key);
        $s2 = $this->slowCache->forget($key);
        return $s1 && $s2;
    }

    public function clear(): bool
    {
        $s1 = $this->fastCache->clear();
        $s2 = $this->slowCache->clear();
        return $s1 && $s2;
    }

    /*
     * - **has**: Versucht zuerst den schnelleren Layer. Im Falle eines Hits im "slowCache" wird der Wert aufgewärmt/gecached, damit zukünftige Zugriffe wieder schneller sind.

     */
    public function has(string $key): bool
    {
        // Schneller Check im Fast-Cache
        if ($this->fastCache->has($key)) {
            return true;
        }
        // Ggf. im Slow-Cache prüfen (und dann in Fast-Cache "aufwärmen")
        $slowHit = $this->slowCache->get($key);
        if ($slowHit->isHit) {
            if($this->shouldCacheInFast($slowHit->value)) {
                $this->fastCache->set($key, $slowHit->value, self::FAST_CACHE_TTL);
            }

            return true;
        }
        return false;

    }

    /*
     * - **remember**: Holt sich das Item per `get` (inkl. aller Multi-Level-Vorteile, wie bereits vorhanden). Wenn nicht im Cache, wird das Callback ausgeführt, gespeichert und als Treffer zurückgegeben.
     */

    public function remember(string $key, callable $callback, int $ttl = 3600): CacheItem
    {
        $item = $this->get($key);
        if ($item->isHit) {
            return $item;
        }

        // Wert generieren, speichern und zurückgeben
        $value = $callback();
        $this->set($key, $value, $ttl);
        // Erstelle neuen CacheItem als Treffer
        return CacheItem::hit($key, $value);

    }

    private function shouldCacheInFast(mixed $value): bool
    {
        // Wenn es bereits ein String ist (serialisiert), nutze dessen Länge
        if (is_string($value)) {
            return strlen($value) <= self::MAX_FAST_CACHE_SIZE;
        }

        // Für Arrays: Schnelle Heuristik ohne Serialisierung
        if (is_array($value)) {
            $elementCount = count($value, COUNT_RECURSIVE);
            // Grobe Schätzung: 50 Bytes pro Element
            $estimatedSize = $elementCount * 50;
            return $estimatedSize <= self::MAX_FAST_CACHE_SIZE;
        }

        // Für Objekte: Konservativ - nicht in fast cache
        if (is_object($value)) {
            return false;
        }

        // Primitive Typen: Immer cachen
        return true;
    }
}
