<?php

namespace Gestoreasy\Core\Cache;

use Redis;
use Exception;

/**
 * Serviço de Cache usando Redis
 * 
 * Fornece cache distribuído para melhorar performance do sistema.
 * Suporta cache de consultas, relatórios, sessões e configurações.
 */
class CacheService {
    private ?Redis $redis = null;
    private bool $enabled = false;
    private string $prefix = 'gestoreasy:';
    private int $defaultTtl = 3600; // 1 hora

    public function __construct() {
        $this->enabled = $this->initRedis();
    }

    /**
     * Inicializa conexão com Redis
     */
    private function initRedis(): bool {
        try {
            $host = $_ENV['REDIS_HOST'] ?? '127.0.0.1';
            $port = (int)($_ENV['REDIS_PORT'] ?? 6379);
            $password = $_ENV['REDIS_PASSWORD'] ?? null;
            $database = (int)($_ENV['REDIS_DATABASE'] ?? 0);

            if (!extension_loaded('redis')) {
                error_log('Redis extension not loaded. Cache will be disabled.');
                return false;
            }

            $this->redis = new Redis();
            $connected = $this->redis->connect($host, $port, 2.0);
            
            if (!$connected) {
                error_log('Failed to connect to Redis. Cache will be disabled.');
                return false;
            }

            if ($password !== null) {
                $this->redis->auth($password);
            }

            $this->redis->select($database);
            $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_JSON);
            $this->redis->setOption(Redis::OPT_PREFIX, $this->prefix);

            return true;
        } catch (Exception $e) {
            error_log('Redis initialization error: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Verifica se o cache está habilitado
     */
    public function isEnabled(): bool {
        return $this->enabled && $this->redis !== null;
    }

    /**
     * Obtém valor do cache
     * 
     * @param string $key Chave do cache
     * @param mixed $default Valor padrão se não encontrar
     * @return mixed
     */
    public function get(string $key, $default = null) {
        if (!$this->isEnabled()) {
            return $default;
        }

        try {
            $value = $this->redis->get($key);
            return $value !== false ? $value : $default;
        } catch (Exception $e) {
            error_log('Cache get error: ' . $e->getMessage());
            return $default;
        }
    }

    /**
     * Define valor no cache
     * 
     * @param string $key Chave do cache
     * @param mixed $value Valor a armazenar
     * @param int|null $ttl Tempo de vida em segundos (null = default)
     * @return bool
     */
    public function set(string $key, $value, ?int $ttl = null): bool {
        if (!$this->isEnabled()) {
            return false;
        }

        try {
            $ttl = $ttl ?? $this->defaultTtl;
            return $this->redis->setex($key, $ttl, $value);
        } catch (Exception $e) {
            error_log('Cache set error: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Remove valor do cache
     * 
     * @param string $key Chave do cache
     * @return bool
     */
    public function delete(string $key): bool {
        if (!$this->isEnabled()) {
            return false;
        }

        try {
            return $this->redis->del($key) > 0;
        } catch (Exception $e) {
            error_log('Cache delete error: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Remove múltiplas chaves do cache
     * 
     * @param array $keys Array de chaves
     * @return int Número de chaves removidas
     */
    public function deleteMultiple(array $keys): int {
        if (!$this->isEnabled() || empty($keys)) {
            return 0;
        }

        try {
            return $this->redis->del(...$keys);
        } catch (Exception $e) {
            error_log('Cache deleteMultiple error: ' . $e->getMessage());
            return 0;
        }
    }

    /**
     * Remove todas as chaves que correspondem a um padrão
     * 
     * @param string $pattern Padrão (ex: "products:*")
     * @return int Número de chaves removidas
     */
    public function deleteByPattern(string $pattern): int {
        if (!$this->isEnabled()) {
            return 0;
        }

        try {
            $keys = $this->redis->keys($pattern);
            if (empty($keys)) {
                return 0;
            }
            return $this->redis->del(...$keys);
        } catch (Exception $e) {
            error_log('Cache deleteByPattern error: ' . $e->getMessage());
            return 0;
        }
    }

    /**
     * Verifica se uma chave existe no cache
     * 
     * @param string $key Chave do cache
     * @return bool
     */
    public function has(string $key): bool {
        if (!$this->isEnabled()) {
            return false;
        }

        try {
            return $this->redis->exists($key) > 0;
        } catch (Exception $e) {
            error_log('Cache has error: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Incrementa valor numérico no cache
     * 
     * @param string $key Chave do cache
     * @param int $value Valor a incrementar (padrão: 1)
     * @return int Novo valor
     */
    public function increment(string $key, int $value = 1): int {
        if (!$this->isEnabled()) {
            return 0;
        }

        try {
            return $this->redis->incrBy($key, $value);
        } catch (Exception $e) {
            error_log('Cache increment error: ' . $e->getMessage());
            return 0;
        }
    }

    /**
     * Decrementa valor numérico no cache
     * 
     * @param string $key Chave do cache
     * @param int $value Valor a decrementar (padrão: 1)
     * @return int Novo valor
     */
    public function decrement(string $key, int $value = 1): int {
        if (!$this->isEnabled()) {
            return 0;
        }

        try {
            return $this->redis->decrBy($key, $value);
        } catch (Exception $e) {
            error_log('Cache decrement error: ' . $e->getMessage());
            return 0;
        }
    }

    /**
     * Obtém ou define valor (cache-aside pattern)
     * 
     * @param string $key Chave do cache
     * @param callable $callback Função que retorna o valor se não estiver em cache
     * @param int|null $ttl Tempo de vida em segundos
     * @return mixed
     */
    public function remember(string $key, callable $callback, ?int $ttl = null) {
        $value = $this->get($key);
        
        if ($value !== null) {
            return $value;
        }

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

    /**
     * Limpa todo o cache (use com cuidado!)
     * 
     * @return bool
     */
    public function flush(): bool {
        if (!$this->isEnabled()) {
            return false;
        }

        try {
            return $this->redis->flushDB();
        } catch (Exception $e) {
            error_log('Cache flush error: ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Obtém estatísticas do cache
     * 
     * @return array
     */
    public function getStats(): array {
        if (!$this->isEnabled()) {
            return [
                'enabled' => false,
                'info' => null
            ];
        }

        try {
            $info = $this->redis->info();
            return [
                'enabled' => true,
                'connected_clients' => $info['connected_clients'] ?? 0,
                'used_memory_human' => $info['used_memory_human'] ?? '0B',
                'keyspace_hits' => $info['keyspace_hits'] ?? 0,
                'keyspace_misses' => $info['keyspace_misses'] ?? 0,
                'hit_rate' => $this->calculateHitRate($info),
            ];
        } catch (Exception $e) {
            error_log('Cache stats error: ' . $e->getMessage());
            return [
                'enabled' => true,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Calcula taxa de acerto do cache
     */
    private function calculateHitRate(array $info): float {
        $hits = (int)($info['keyspace_hits'] ?? 0);
        $misses = (int)($info['keyspace_misses'] ?? 0);
        $total = $hits + $misses;
        
        if ($total === 0) {
            return 0.0;
        }

        return round(($hits / $total) * 100, 2);
    }

    /**
     * Gera chave de cache com namespace
     * 
     * @param string $namespace Namespace (ex: "products", "reports")
     * @param string $key Chave
     * @return string
     */
    public static function key(string $namespace, string $key): string {
        return "{$namespace}:{$key}";
    }

    /**
     * Gera chave de cache com tenant
     * 
     * @param string $namespace Namespace
     * @param string $key Chave
     * @param string|null $tenantId ID do tenant (opcional, usa contexto atual se não fornecido)
     * @return string
     */
    public static function tenantKey(string $namespace, string $key, ?string $tenantId = null): string {
        if ($tenantId === null) {
            $tenantId = \Gestoreasy\Core\Tenant\TenantContext::getTenantId() ?? 'default';
        }
        return "tenant:{$tenantId}:{$namespace}:{$key}";
    }
}

