<?php

if (!defined('ABSPATH')) exit;

use function Env\env;

class Rdg_Blocks_Cache
{
    private $cache;

    function __construct() {
        $this->cache = new Redis();
    }

    public function getRedisConnect() : ?bool {
        try {
            // Get the target database first
            $redis_db = defined('WP_REDIS_DATABASE') ? WP_REDIS_DATABASE : 0;
            
            // Check if already connected by attempting a ping
            try {
                if ($this->cache->ping()) {
                    // Connection exists, but ensure we're on the correct database
                    $this->cache->select($redis_db);
                    return true;
                }
            } catch (\Exception $e) {
                // Not connected, will connect below
            }

            // Always attempt to connect first with the configured host/port
            // This ensures we don't fallback to 127.0.0.1 by default
            $this->cache->connect(WP_REDIS_HOST, WP_REDIS_PORT);
            
            // Only authenticate if password is provided and not empty
            // Platform.sh Redis doesn't use password, local/other envs might
            if (!empty(WP_REDIS_PASSWORD)) {
                $this->cache->auth(WP_REDIS_PASSWORD);
            }

            // Select Redis database (defaults to 0 if not defined)
            // Uses the same database as WordPress Redis Object Cache for consistency
            $this->cache->select($redis_db);

            // Verify connection with ping
            if ($this->cache->ping()) {
                return true;
            }

            return null;
        } catch (\Exception $exception) {
            // Check if error is "already connected" - that's actually OK
            if (strpos($exception->getMessage(), 'already connected') !== false || 
                strpos($exception->getMessage(), 'Connection already established') !== false) {
                // Already connected, verify with ping and select correct database
                try {
                    if ($this->cache->ping()) {
                        $redis_db = defined('WP_REDIS_DATABASE') ? WP_REDIS_DATABASE : 0;
                        $this->cache->select($redis_db);
                        return true;
                    }
                } catch (\Exception $e) {
                    // Ping failed, connection is bad
                }
            }
            
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / getRedisConnect: %s', $exception->getMessage()),
                'host' => WP_REDIS_HOST,
                'port' => WP_REDIS_PORT,
            ], true));
        }

        return null;
    }

    public function getBlocksCacheListName() : string {
        return RDGBLOCKS_NAME_SLUG.':cache';
    }

    public function getCacheFailedName($name) : string {
        return $name.':failed';
    }

    public function getCacheList() : array {
        if (!is_null($this->cache)) {
            // Ensure connection is active and correct database is selected
            $connected = $this->getRedisConnect();
            if (!$connected) {
                error_log('RDG Blocks / getCacheList: Redis connection failed.');
                return [];
            }
            
            return $this->cache->lrange($this->getBlocksCacheListName(), 0, -1);
        }

        return [];
    }

    public function addCacheList($name, $refresh, $data) : bool {
        try {
            // Verify Redis connection is established
            if (is_null($this->cache)) {
                error_log(sprintf('RDG Blocks / addCacheList: Redis cache object is null for %s', $name));
                return false;
            }

            // Ensure connection is active - verify with getRedisConnect
            $connected = $this->getRedisConnect();
            if (!$connected) {
                error_log(sprintf('RDG Blocks / addCacheList: Redis connection failed for %s', $name));
                return false;
            }

            $this->cache->rpush($this->getBlocksCacheListName(), $name);
            
            // Ensure minimum TTL of 3 hours to prevent premature expiration
            // Blocks with refresh = 1 hour will get extended to 3 hours minimum
            $ttl_seconds = max($refresh * 60 * 60, 3 * 60 * 60);
            $this->cache->set($name, json_encode($data), $ttl_seconds);
            
            return true;
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / addCacheList: %s', $exception->getMessage()),
                'name' => $name,
                'refresh' => $refresh,
                'trace' => $exception->getTraceAsString(),
            ], true));
        }

        return false;
    }

    public function updateCacheFailed($name, $cache_refresh) : int|null {
        try {
            if (!is_null($this->cache)) {
                $failed_name = $this->getCacheFailedName($name);
                $cache_failed_exist = $this->cache->get($failed_name);
                $this->cache->expire($name, $cache_refresh * 60 * 60);

                if (empty($cache_failed_exist)) {
                    $this->cache->incr($failed_name);
                    $this->cache->expire($failed_name, RDG_BLOCK_CACHE_FAILED_RETRY_DELAY);

                    return 1;
                }

                $this->cache->incr($failed_name);

                return $cache_failed_exist + 1;
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / getCacheFailed: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return null;
    }

    public function getCacheBlock($name) : bool|string {
        try {
            if (!is_null($this->cache)) {
                // Ensure connection is active and correct database is selected
                $connected = $this->getRedisConnect();
                if (!$connected) {
                    error_log(sprintf('RDG Blocks / getCacheBlock: Redis connection failed for %s', $name));
                    return false;
                }
                
                return $this->cache->get($name);
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / getCacheBlock: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return false;
    }

    public function getCacheBlockTimeSeg($name) : int {
        try {
            if (!is_null($this->cache)) {
                return $this->cache->ttl($name);
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / getCacheBlockTime: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return 0;
    }

    public function getCacheBlockTime($name) : bool|string {
        try {
            if (!is_null($this->cache)) {
                $time = $this->cache->ttl($name);

                if ($time > 3600) {
                    return round($time / 3600, 2) . 'h';
                } elseif ($time > 0) {
                    return round($time / 60, 1) . 'm';
                }
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / getCacheBlockTime: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return false;
    }

    public function delAllCache(): bool {
        try {
            if (!is_null($this->cache)) {
                $cache_blocks = $this->getCacheList();

                foreach ($cache_blocks as $saved_cache) {
                    $this->delCacheBlock($saved_cache);
                }

                $this->cache->del($this->getBlocksCacheListName());

                return true;
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / delAllCache: %s', $exception->getMessage()),
            ], true));
        }

        return false;
    }

    public function delCacheBlock($name) : bool {
        try {
            if (!is_null($this->cache)) {
                $this->cache->del($name);
                $this->cache->lrem($this->getBlocksCacheListName(), $name);

                $failed_name = $this->getCacheFailedName($name);
                $this->cache->del($failed_name);

                return true;
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / delCacheBlock: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return false;
    }

    public function updateCacheBlock($name, $attributes, $data) : bool {
        try {
            if (!is_null($this->cache)) {
                // Ensure connection is active and correct database is selected
                $connected = $this->getRedisConnect();
                if (!$connected) {
                    error_log(sprintf('RDG Blocks / updateCacheBlock: Redis connection failed for %s', $name));
                    return false;
                }
                
                $refresh = !empty($attributes['api_attributes']['cache_refresh']) ? $attributes['api_attributes']['cache_refresh'] : 1;
                $data_update['api_response'] = $data;
                $data_update['api_attributes'] = $attributes['api_attributes'];                            
                $data_update['items'] = is_countable($data) ? count($data) : 0;

                $this->cache->set($name, json_encode($data_update), $refresh * 60 * 60);

                $failed_name = $this->getCacheFailedName($name);
                $this->cache->del($failed_name);

                return true;
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / updateCacheBlock: %s', $exception->getMessage()),
                'name' => $name,
            ], true));
        }

        return false;
    }

    public function clearCacheBlock($blocks_list, $page): void {
        try {
            if (!is_null($this->cache)) {
                $in_array_apply = (is_array($blocks_list) && count($blocks_list) > 0) ? true : false;
                $cache_blocks = $this->getCacheList();

                foreach ($cache_blocks as $saved_cache) {
                    $params = explode(':', $saved_cache);
                    $page_saved = $params[1].':'.$params[2];

                    if ($page == $page_saved && ($in_array_apply && !in_array($saved_cache, $blocks_list) || !$in_array_apply)) {
                        $this->delCacheBlock($saved_cache);
                    }
                }
            }
        } catch (\Exception $exception) {
            error_log(print_r([
                'exception' => sprintf('RDG Blocks / clearCacheBlock: %s', $exception->getMessage()),
                'page' => $page,
                'blocks_list' => $blocks_list,
            ], true));
        }
    }
}
