<?php

/**
 * Redirect Cache
 *
 * High-performance caching layer for redirect lookups
 *
 * @package ProRank\SEO\Core\Cache
 * @since   1.5.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\Cache;

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


/**
 * RedirectCache class
 */


class RedirectCache {
    
    /**
     * Cache group
     *
     * @var string
     */
    private const CACHE_GROUP = 'prorank_redirects';
    
    /**
     * Cache expiry time (seconds)
     *
     * @var int
     */
    private const CACHE_EXPIRY = 3600; // 1 hour
    
    /**
     * Memory cache for current request
     *
     * @var array
     */
    private static array $memory_cache = [];

    /**
     * Cached table existence check per prefix.
     *
     * @var array
     */
    private static array $redirect_table_exists = [];

    /**
     * Check whether persistent redirect caching is enabled.
     *
     * @return bool
     */
    private static function is_cache_enabled(): bool {
        return (bool) get_option('prorank_redirect_caching_enabled', true);
    }

    /**
     * Get redirect cache expiry.
     *
     * @return int
     */
    private static function get_cache_expiry(): int {
        $expiry = (int) get_option('prorank_redirect_cache_expiry', self::CACHE_EXPIRY);
        return max(0, $expiry);
    }
    
    /**
     * Get redirect from cache
     *
     * @param string $source_url Source URL
     * @return object|null Redirect object or null
     */
    public static function get(string $source_url): ?object {
        // Check memory cache first (fastest)
        if (isset(self::$memory_cache[$source_url])) {
            return self::$memory_cache[$source_url];
        }

        if (!self::is_cache_enabled()) {
            return null;
        }
        
        // Check object cache (if available)
        if (wp_using_ext_object_cache()) {
            $cache_key = self::get_cache_key($source_url);
            $cached = wp_cache_get($cache_key, self::CACHE_GROUP);
            
            if ($cached !== false) {
                // Store in memory cache for this request
                self::$memory_cache[$source_url] = $cached;
                return $cached;
            }
        }
        
        // Check transient cache (fallback)
        $transient_key = self::get_transient_key($source_url);
        $cached = get_transient($transient_key);
        
        if ($cached !== false) {
            // Store in memory cache for this request
            self::$memory_cache[$source_url] = $cached;
            return $cached;
        }
        
        return null;
    }
    
    /**
     * Set redirect in cache
     *
     * @param string $source_url Source URL
     * @param object $redirect Redirect object
     * @return void
     */
    public static function set(string $source_url, object $redirect): void {
        // Store in memory cache
        self::$memory_cache[$source_url] = $redirect;

        if (!self::is_cache_enabled()) {
            return;
        }

        $cache_expiry = self::get_cache_expiry();
        
        // Store in object cache
        if (wp_using_ext_object_cache()) {
            $cache_key = self::get_cache_key($source_url);
            wp_cache_set($cache_key, $redirect, self::CACHE_GROUP, $cache_expiry);
        }
        
        // Store in transient cache (fallback)
        $transient_key = self::get_transient_key($source_url);
        set_transient($transient_key, $redirect, $cache_expiry);
    }
    
    /**
     * Delete redirect from cache
     *
     * @param string $source_url Source URL
     * @return void
     */
    public static function delete(string $source_url): void {
        // Remove from memory cache
        unset(self::$memory_cache[$source_url]);
        
        // Remove from object cache
        if (wp_using_ext_object_cache()) {
            $cache_key = self::get_cache_key($source_url);
            wp_cache_delete($cache_key, self::CACHE_GROUP);
        }
        
        // Remove from transient cache
        $transient_key = self::get_transient_key($source_url);
        delete_transient($transient_key);
    }
    
    /**
     * Flush all redirect caches
     *
     * @return void
     */
    public static function flush(): void {
        // Clear memory cache
        self::$memory_cache = [];
        
        // Clear object cache group
        if (wp_using_ext_object_cache()) {
            wp_cache_flush_group(self::CACHE_GROUP);
        }
        
        // Clear transients
        global $wpdb;
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$wpdb->options} 
                WHERE option_name LIKE %s 
                OR option_name LIKE %s",
                '_transient_prorank_redirect_%',
                '_transient_timeout_prorank_redirect_%'
            )
        );
    }
    
    /**
     * Preload frequently used redirects
     *
     * @return void
     */
    public static function preload(): void {
        global $wpdb;
        if (!self::is_cache_enabled()) {
            return;
        }
        if (!self::redirects_table_exists()) {
            return;
        }

        // Get top 50 most hit redirects
        $table = $wpdb->prefix . 'prorank_redirects';
        $redirects = $wpdb->get_results(
            "SELECT * FROM {$table} 
            WHERE status = 'active' 
            ORDER BY hits DESC 
            LIMIT 50"
        );
        
        foreach ($redirects as $redirect) {
            self::set($redirect->source_url, $redirect);
        }
    }
    
    /**
     * Get cache key for source URL
     *
     * @param string $source_url Source URL
     * @return string Cache key
     */
    private static function get_cache_key(string $source_url): string {
        return 'redirect_' . md5($source_url);
    }
    
    /**
     * Get transient key for source URL
     *
     * @param string $source_url Source URL
     * @return string Transient key
     */
    private static function get_transient_key(string $source_url): string {
        // Transient keys are limited to 172 characters
        return 'prorank_redirect_' . substr(md5($source_url), 0, 32);
    }
    
    /**
     * Warm cache on init
     *
     * Uses atomic locking to prevent race conditions when multiple
     * concurrent requests attempt to warm the cache simultaneously.
     *
     * @return void
     */
    public static function warm_cache(): void {
        if (!self::is_cache_enabled()) {
            return;
        }
        if (!self::redirects_table_exists()) {
            return;
        }

        // Only warm cache if not already warmed recently
        $last_warm = get_transient('prorank_redirect_cache_warmed');
        if ($last_warm) {
            return;
        }

        // Attempt to acquire lock atomically
        // Use add_option instead of set_transient for atomic check-and-set
        global $wpdb;
        $lock_key = '_transient_prorank_redirect_cache_lock';
        $lock_timeout = 30; // Lock expires after 30 seconds

        // Try to acquire lock atomically
        $lock_acquired = $wpdb->query(
            $wpdb->prepare(
                "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload)
                VALUES (%s, %s, 'no')",
                $lock_key,
                time()
            )
        );

        // If lock wasn't acquired, check if it's stale
        if (!$lock_acquired) {
            $lock_time = get_option($lock_key);
            if ($lock_time && (time() - (int) $lock_time) > $lock_timeout) {
                // Lock is stale, delete it and try again
                delete_option($lock_key);
                $lock_acquired = $wpdb->query(
                    $wpdb->prepare(
                        "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload)
                        VALUES (%s, %s, 'no')",
                        $lock_key,
                        time()
                    )
                );
            }
        }

        // If we still couldn't acquire lock, another process is warming
        if (!$lock_acquired) {
            return;
        }

        try {
            // Preload frequently used redirects
            self::preload();

            // Mark cache as warmed
            set_transient('prorank_redirect_cache_warmed', time(), 3600);
        } finally {
            // Always release the lock
            delete_option($lock_key);
        }
    }
    
    /**
     * Get cache statistics
     *
     * @return array Cache statistics
     */
    public static function get_stats(): array {
        global $wpdb;

        // Count transients
        $transient_count = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT COUNT(*) FROM {$wpdb->options}
                WHERE option_name LIKE %s",
                '_transient_prorank_redirect_%'
            )
        );

        return [
            'memory_cache_count' => count(self::$memory_cache),
            'transient_cache_count' => (int) $transient_count,
            'using_object_cache' => wp_using_ext_object_cache(),
            'cache_enabled' => self::is_cache_enabled(),
            'cache_expiry' => self::get_cache_expiry(),
        ];
    }

    /**
     * Check if redirects table exists.
     *
     * @return bool
     */
    private static function redirects_table_exists(): bool {
        global $wpdb;

        if (!isset($wpdb)) {
            return false;
        }

        $prefix = $wpdb->prefix;

        if (array_key_exists($prefix, self::$redirect_table_exists)) {
            return self::$redirect_table_exists[$prefix];
        }

        $table = $wpdb->prefix . 'prorank_redirects';
        $result = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
        self::$redirect_table_exists[$prefix] = ($result === $table);

        return self::$redirect_table_exists[$prefix];
    }

    /**
     * Get cached regex redirects list
     *
     * 2025 Enhancement: Cache the list of regex redirects to avoid repeated database queries
     *
     * @return array|null Array of regex redirects or null if not cached
     */
    public static function get_regex_redirects(): ?array {
        $cache_key = 'regex_redirects_list';

        // Check memory cache first (fastest)
        if (isset(self::$memory_cache[$cache_key])) {
            return self::$memory_cache[$cache_key];
        }

        if (!self::is_cache_enabled()) {
            return null;
        }

        // Check object cache (if available)
        if (wp_using_ext_object_cache()) {
            $cached = wp_cache_get($cache_key, self::CACHE_GROUP);

            if ($cached !== false) {
                // Store in memory cache for this request
                self::$memory_cache[$cache_key] = $cached;
                return $cached;
            }
        }

        // Check transient cache (fallback)
        $transient_key = 'prorank_redirect_regex_list';
        $cached = get_transient($transient_key);

        if ($cached !== false) {
            // Store in memory cache for this request
            self::$memory_cache[$cache_key] = $cached;
            return $cached;
        }

        return null;
    }

    /**
     * Set cached regex redirects list
     *
     * 2025 Enhancement: Cache the list of regex redirects with priority ordering
     *
     * @param array $redirects Array of redirect objects
     * @return void
     */
    public static function set_regex_redirects(array $redirects): void {
        $cache_key = 'regex_redirects_list';

        // Store in memory cache
        self::$memory_cache[$cache_key] = $redirects;

        if (!self::is_cache_enabled()) {
            return;
        }

        $cache_expiry = self::get_cache_expiry();

        // Store in object cache
        if (wp_using_ext_object_cache()) {
            wp_cache_set($cache_key, $redirects, self::CACHE_GROUP, $cache_expiry);
        }

        // Store in transient cache (fallback)
        $transient_key = 'prorank_redirect_regex_list';
        set_transient($transient_key, $redirects, $cache_expiry);
    }

    /**
     * Clear regex redirects cache
     *
     * 2025 Enhancement: Clear cached regex redirects when redirects are modified
     *
     * @return void
     */
    public static function clear_regex_redirects(): void {
        $cache_key = 'regex_redirects_list';

        // Remove from memory cache
        unset(self::$memory_cache[$cache_key]);

        // Remove from object cache
        if (wp_using_ext_object_cache()) {
            wp_cache_delete($cache_key, self::CACHE_GROUP);
        }

        // Remove from transient cache
        $transient_key = 'prorank_redirect_regex_list';
        delete_transient($transient_key);
    }
}
