<?php

namespace App\Framework\Cache;

final readonly class CompressionCacheDecorator implements Cache
{
    public function __construct(
        private Cache                $innerCache,
        private CompressionAlgorithm $algorithm,
        private Serializer           $serializer
    ) {}

    public function get(string $key): CacheItem
    {
        $item = $this->innerCache->get($key);
        if (!$item->isHit || !is_string($item->value)) {
            return $item;
        }

        $value = $item->value;

        if ($this->algorithm->isCompressed($value)) {
            $value = $this->algorithm->decompress($value);
        }

        try {
            // Versuche direkt zu deserialisieren.
            // Schlägt dies fehl, wird der rohe (aber dekomprimierte) Wert zurückgegeben.
            $unserialized = $this->serializer->unserialize($value);
            return CacheItem::hit($key, $unserialized);
        } catch (\Throwable $e) {
            // Das ist ein erwartetes Verhalten, wenn der Wert ein einfacher String war.
            // Optional: Loggen des Fehlers für Debugging-Zwecke.
            // error_log("CompressionCacheDecorator: Deserialization failed for key: {$key}. Assuming raw value.");
            return CacheItem::hit($key, $value);
        }

        //LEGACY:

        /*if ($this->algorithm->isCompressed($item->value)) {
            $decompressed = $this->algorithm->decompress($item->value);

            // Prüfe ob der Inhalt serialisiert wurde
            if ($this->isSerialized($decompressed)) {
                try {
                    $unserialized = $this->serializer->unserialize($decompressed);
                    return CacheItem::hit($key, $unserialized);
                } catch (\Throwable $e) {
                    // Fallback bei Deserialisierung-Fehler
                    error_log("CompressionCacheDecorator: Deserialization failed for key: {$key}, Error: " . $e->getMessage());
                }
            }

            // Wenn nicht serialisiert oder Deserialisierung fehlgeschlagen, gib dekomprimierten String zurück
            return CacheItem::hit($key, $decompressed);
        }

        if($this->isSerialized($item->value)) {
            try {
                $unserialized = $this->serializer->unserialize($item->value);
                return CacheItem::hit($key, $unserialized);
            } catch (\Throwable $e) {
                error_log("CompressionCacheDecorator: Deserialization failed for key: {$key}, Error: " . $e->getMessage());
            }
        }

        return $item;*/
    }

    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        if (!is_string($value)) {
            $value = $this->serializer->serialize($value);
        }
        $compressed = $this->algorithm->compress($value);
        return $this->innerCache->set($key, $compressed, $ttl);
    }

    public function has(string $key): bool
    {
        return $this->innerCache->has($key);
    }

    public function forget(string $key): bool
    {
        return $this->innerCache->forget($key);
    }

    public function clear(): bool
    {
        return $this->innerCache->clear();
    }

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

    /**
     * Prüft ob ein String serialisierte PHP-Daten enthält
     */
    /*
    private function isSerialized(string $data): bool
    {
        // Leere Strings oder sehr kurze Strings können nicht serialisiert sein
        if (strlen($data) < 4) {
            return false;
        }

        // Prüfe auf NULL-Wert (serialisiert als 'N;')
        if ($data === 'N;') {
            return true;
        }

        // Prüfe auf boolean false (serialisiert als 'b:0;')
        if ($data === 'b:0;') {
            return true;
        }

        // Prüfe auf boolean true (serialisiert als 'b:1;')
        if ($data === 'b:1;') {
            return true;
        }

        // Prüfe auf typische serialize() Patterns:
        // a:N: (array mit N Elementen)
        // s:N: (string mit N Zeichen)
        // i:N; (integer mit Wert N)
        // d:N; (double/float mit Wert N)
        // O:N: (object mit Klassennamen der Länge N)
        // b:N; (boolean mit Wert N)
        // r:N; (reference)
        // R:N; (reference)
        if (preg_match('/^[asdObRr]:[0-9]+[:|;]/', $data)) {
            return true;
        }

        // Prüfe auf integer Pattern: i:Zahl;
        if (preg_match('/^i:-?[0-9]+;/', $data)) {
            return true;
        }

        // Prüfe auf float Pattern: d:Zahl;
        if (preg_match('/^d:-?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?;/', $data)) {
            return true;
        }

        // Zusätzliche Validierung: Versuche tatsächlich zu deserialisieren
        // aber nur bei verdächtigen Patterns, um Performance zu schonen
        if (strlen($data) < 1000 && (
            str_starts_with($data, 'a:') ||
            str_starts_with($data, 'O:') ||
            str_starts_with($data, 's:')
        )) {
            $test = @unserialize($data);
            return $test !== false || $data === 'b:0;';
        }

        return false;
    }
    */
}
