<?php

namespace App\Framework\Cache\Driver;

use App\Framework\Cache\CacheDriver;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CachePrefix;
use App\Framework\Cache\Serializer;
use Predis\Client as Redis;

final readonly class RedisCache implements CacheDriver
{
    private Redis $redis;

    public function __construct(
        string $host = '127.0.0.1',
        int $port = 6379,
        ?string $password = null,
        int $db = 0,
        #private Serializer $serializer = new Serializer\PhpSerializer(),
        private string $prefix = 'cache:'
    )
    {
        $this->redis = new Redis(
            parameters: [
                'scheme' => 'tcp',
                'timeout' => 1.0,
                'read_write_timeout' => 1.0,
                'host' => $host,
                'port' => $port,
            ]
        );

        #$this->redis->connect();

        if ($password) {
            $this->redis->auth($password);
        }
        $this->redis->select($db);
    }

    private function prefixKey(string $key): string
    {
        return $this->prefix . $key;
    }

    public function get(string $key): CacheItem
    {
        $key = $this->prefixKey($key);

        $data = $this->redis->get($key);
        if ($data === null) {
            return CacheItem::miss($key);
        }
        #$decoded = $this->serializer->unserialize($data); // oder json_decode($data, true)
        $decoded = $data;
        /*if (!is_array($decoded) || !array_key_exists('value', $decoded)) {
            return CacheItem::miss($key);
        }*/

        // TODO: REMOVE TTL
        $ttl = $this->redis->ttl($key);
        $expiresAt = $ttl > 0 ? (time() + $ttl) : null;
        return CacheItem::hit(
            key  : $key,
            value: $decoded,
        );
    }

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

        #$payload = $this->serializer->serialize($value); #war: ['value' => $value]

        $payload = $value;

        if ($ttl !== null) {
            return $this->redis->setex($key, $ttl, $payload)->getPayload() === 'OK';
        }
        return $this->redis->set($key, $payload)->getPayload() === 'OK';
    }

    public function has(string $key): bool
    {
        $key = $this->prefixKey($key);

        return $this->redis->exists($key) > 0;
    }

    public function forget(string $key): bool
    {
        $key = $this->prefixKey($key);

        return $this->redis->del($key) > 0;
    }

    public function clear(): bool
    {
        try {
            $patterns = array_map(
                fn($prefix) => $prefix->value . '*',
                CachePrefix::cases()
            );

            foreach($patterns as $pattern) {
                $this->clearByPattern($pattern);
            }
            return true;
        }  catch (\Throwable $e) {
            return false;
        }
    }

    private function clearByPattern(string $pattern): void
    {
        $cursor = 0;
        $batchSize = 1000; // Batch-Größe für bessere Performance

        do {
            $result = $this->redis->scan($cursor, [
                'MATCH' => $pattern,
                'COUNT' => $batchSize
            ]);

            $cursor = $result[0];
            $keys = $result[1];

            if (!empty($keys)) {
                $this->redis->del($keys);
            }
        } while ($cursor !== 0);
    }
}
