<?php

declare(strict_types=1);

namespace App\Framework\Cache\Driver;

use App\Framework\Cache\CacheDriver;
use App\Framework\Cache\CacheItem;
use App\Framework\Cache\CacheKey;
use App\Framework\Cache\CachePrefix;
use App\Framework\Cache\CacheResult;
use App\Framework\Cache\Contracts\Scannable;
use App\Framework\Redis\RedisConnectionInterface;
use Redis;

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

    public function __construct(
        private RedisConnectionInterface $connection,
        private string $prefix = 'cache:'
    ) {
        $this->redis = $this->connection->getClient();
    }

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

    public function get(CacheKey ...$keys): CacheResult
    {
        if (empty($keys)) {
            return CacheResult::empty();
        }

        // Use Redis MGET for batch operations
        $prefixedKeys = array_map(fn ($key) => $this->prefixKey($key), $keys);
        $values = $this->redis->mget($prefixedKeys);

        // Handle case where Redis MGET fails and returns false
        if ($values === false) {
            // Fallback to individual GET operations
            $values = [];
            foreach ($prefixedKeys as $prefixedKey) {
                $values[] = $this->redis->get($prefixedKey);
            }
        }

        $items = [];
        foreach ($keys as $index => $key) {
            $value = $values[$index] ?? false;
            if ($value !== false) {
                $items[] = CacheItem::hit($key, $value);
            } else {
                $items[] = CacheItem::miss($key);
            }
        }

        return CacheResult::fromItems(...$items);
    }

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

        // Use Redis pipeline for batch operations
        $pipe = $this->redis->multi(Redis::PIPELINE);

        foreach ($items as $item) {
            $prefixedKey = $this->prefixKey($item->key);

            if ($item->ttl !== null) {
                $ttlSeconds = $item->ttl->toCacheSeconds();
                $pipe->setex($prefixedKey, $ttlSeconds, $item->value);
            } else {
                $pipe->set($prefixedKey, $item->value);
            }
        }

        $results = $pipe->exec();

        // Check if all operations were successful
        foreach ($results as $result) {
            if ($result !== true) {
                return false;
            }
        }

        return true;
    }

    public function has(CacheKey ...$keys): array
    {
        if (empty($keys)) {
            return [];
        }

        // Use Redis pipeline for batch existence checks
        $pipe = $this->redis->multi(Redis::PIPELINE);

        foreach ($keys as $key) {
            $prefixedKey = $this->prefixKey($key);
            $pipe->exists($prefixedKey);
        }

        $results = $pipe->exec();

        $hasResults = [];
        foreach ($keys as $index => $key) {
            $keyString = (string)$key;
            $hasResults[$keyString] = ($results[$index] ?? 0) > 0;
        }

        return $hasResults;
    }

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

        $prefixedKeys = array_map(fn ($key) => $this->prefixKey($key), $keys);
        $deletedCount = $this->redis->del($prefixedKeys);

        return $deletedCount > 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 {
            $keys = $this->redis->scan($cursor, $pattern, $batchSize);

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

    // === Scannable Interface Implementation ===

    public function scan(string $pattern, int $limit = 1000): array
    {
        // Convert wildcard pattern to Redis pattern
        $redisPattern = $this->wildcardToRedisPattern($pattern);

        $cursor = 0;
        $matches = [];
        $batchSize = min(100, $limit ?: 100);

        do {
            $keys = $this->redis->scan($cursor, $redisPattern, $batchSize);

            if (! empty($keys)) {
                $matches = array_merge($matches, $keys);

                // Stop if we've reached the limit
                if ($limit > 0 && count($matches) >= $limit) {
                    $matches = array_slice($matches, 0, $limit);

                    break;
                }
            }
        } while ($cursor !== 0);

        return $matches;
    }

    public function scanPrefix(string $prefix, int $limit = 1000): array
    {
        // Redis SCAN with prefix pattern
        $pattern = $prefix . '*';

        $cursor = 0;
        $matches = [];
        $batchSize = min(100, $limit ?: 100);

        do {
            $keys = $this->redis->scan($cursor, $pattern, $batchSize);

            if (! empty($keys)) {
                $matches = array_merge($matches, $keys);

                // Stop if we've reached the limit
                if ($limit > 0 && count($matches) >= $limit) {
                    $matches = array_slice($matches, 0, $limit);

                    break;
                }
            }
        } while ($cursor !== 0);

        return $matches;
    }

    public function getAllKeys(int $limit = 1000): array
    {
        // Scan all keys with * pattern
        return $this->scan('*', $limit);
    }

    public function getScanPerformance(): array
    {
        return [
            'efficient' => true,
            'max_recommended_keys' => 100000,
            'estimated_time_per_1000_keys' => 0.01, // 10ms per 1000 keys
        ];
    }

    /**
     * Convert wildcard pattern to Redis SCAN pattern
     */
    private function wildcardToRedisPattern(string $pattern): string
    {
        // Redis supports * for any characters and ? for single character
        // Our pattern uses * for single level and ** for multi-level

        // Convert ** to * (Redis doesn't distinguish levels)
        $redisPattern = str_replace('**', '*', $pattern);

        // Redis pattern is ready to use
        return $redisPattern;
    }
}
