<?php

declare(strict_types=1);

namespace Flowaxy\Infrastructure\Cache\Drivers;

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

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

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

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

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

        try {
            $this->memcached = new Memcached();
            $host = $_ENV['MEMCACHED_HOST'] ?? '127.0.0.1';
            $port = (int)($_ENV['MEMCACHED_PORT'] ?? 11211);

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

            $this->memcached->setOption(Memcached::OPT_PREFIX_KEY, $this->prefix);
            return true;
        } catch (Throwable $e) {
            try {
                Log::Error('Memcached connection failed', ['exception' => $e]);
            } catch (Throwable $logError) {
                // Ignore
            }
            $this->memcached = 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->memcached->get($this->getKey($key));
            if ($value === false && $this->memcached->getResultCode() === Memcached::RES_NOTFOUND) {
                return $default;
            }
            return $value;
        } catch (Throwable $e) {
            try {
                Log::Warning('Memcached 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 {
            $ttl = $ttl ?? 3600;
            return $this->memcached->set($this->getKey($key), $value, $ttl);
        } catch (Throwable $e) {
            try {
                Log::Warning('Memcached set error', ['exception' => $e, 'key' => $key]);
            } catch (Throwable $logError) {
                // Ignore
            }
            return false;
        }
    }

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

        try {
            $this->memcached->get($this->getKey($key));
            return $this->memcached->getResultCode() === Memcached::RES_SUCCESS;
        } catch (Throwable $e) {
            return false;
        }
    }

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

        try {
            return $this->memcached->delete($this->getKey($key));
        } catch (Throwable $e) {
            return false;
        }
    }

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

        try {
            return $this->memcached->flush();
        } 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->memcached->increment($this->getKey($key), $value) ?: 0;
        } catch (Throwable $e) {
            return 0;
        }
    }

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

        try {
            return $this->memcached->decrement($this->getKey($key), $value) ?: 0;
        } 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;
    }
}
