<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Cache\Drivers;

use Flowaxy\Contracts\Cache\CacheInterface;
use Flowaxy\Support\Facades\Log;
use Redis;
use Throwable;

/**
 * Redis драйвер для кеша
 *
 * @package Flowaxy\Infrastructure\Cache\Drivers
 */
final class RedisCacheDriver implements CacheInterface
{
    private ?Redis $redis = null;
    private string $prefix = 'flowaxy:';

    public function __construct(?Redis $redis = null, string $prefix = 'flowaxy:')
    {
        $this->redis = $redis;
        $this->prefix = $prefix;
    }

    /**
     * Подключиться к Redis
     *
     * @return bool
     */
    private function connect(): bool
    {
        if ($this->redis !== null) {
            return true;
        }

        if (!extension_loaded('redis')) {
            return false;
        }

        try {
            $this->redis = new Redis();
            $host = $_ENV['REDIS_HOST'] ?? '127.0.0.1';
            $port = (int)($_ENV['REDIS_PORT'] ?? 6379);

            if (!$this->redis->connect($host, $port)) {
                $this->redis = null;
                return false;
            }

            $password = $_ENV['REDIS_PASSWORD'] ?? null;
            if ($password !== null) {
                $this->redis->auth($password);
            }

            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('Redis connection failed', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore
            }
            $this->redis = null;
            return false;
        }
    }

    /**
     * Получить ключ с префиксом
     *
     * @param string $key
     * @return string
     */
    private function getKey(string $key): string
    {
        return $this->prefix . $key;
    }

    public function get(string $key, mixed $default = null): mixed
    {
        if (!$this->connect()) {
            return $default;
        }

        try {
            $value = $this->redis->get($this->getKey($key));
            if ($value === false) {
                return $default;
            }
            return unserialize($value, ['allowed_classes' => true]);
        } catch (Throwable $e) {
            try {
                Log::Warning('Redis get error', ['exception' => $e, 'key' => $key]);
            } catch (Throwable $logError) {
                // Ignore
            }
            return $default;
        }
    }

    public function set(string $key, mixed $value, ?int $ttl = null): bool
    {
        if (!$this->connect()) {
            return false;
        }

        try {
            $serialized = serialize($value);
            $ttl = $ttl ?? 3600;
            return $this->redis->setex($this->getKey($key), $ttl, $serialized);
        } catch (Throwable $e) {
            try {
                Log::Warning('Redis set error', ['exception' => $e, 'key' => $key]);
            } catch (Throwable $logError) {
                // Ignore
            }
            return false;
        }
    }

    public function has(string $key): bool
    {
        if (!$this->connect()) {
            return false;
        }

        try {
            return $this->redis->exists($this->getKey($key)) > 0;
        } catch (Throwable $e) {
            return false;
        }
    }

    public function delete(string $key): bool
    {
        if (!$this->connect()) {
            return false;
        }

        try {
            return $this->redis->del($this->getKey($key)) > 0;
        } catch (Throwable $e) {
            return false;
        }
    }

    public function clear(): bool
    {
        if (!$this->connect()) {
            return false;
        }

        try {
            $keys = $this->redis->keys($this->prefix . '*');
            if (!empty($keys)) {
                return $this->redis->del($keys) > 0;
            }
            return true;
        } catch (Throwable $e) {
            return false;
        }
    }

    public function pull(string $key, mixed $default = null): mixed
    {
        $value = $this->get($key, $default);
        if ($value !== $default) {
            $this->delete($key);
        }
        return $value;
    }

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

    public function increment(string $key, int $value = 1): int
    {
        if (!$this->connect()) {
            return 0;
        }

        try {
            return $this->redis->incrBy($this->getKey($key), $value);
        } catch (Throwable $e) {
            return 0;
        }
    }

    public function decrement(string $key, int $value = 1): int
    {
        if (!$this->connect()) {
            return 0;
        }

        try {
            return $this->redis->decrBy($this->getKey($key), $value);
        } catch (Throwable $e) {
            return 0;
        }
    }

    public function remember(string $key, callable $callback, ?int $ttl = null): mixed
    {
        $value = $this->get($key);
        if ($value !== null) {
            return $value;
        }

        $value = $callback();
        $this->set($key, $value, $ttl);
        return $value;
    }
}
