<?php

/**
 * Cache REST API Controller
 *
 * Handles cache-related endpoints
 *
 * @package ProRank\SEO\Core\RestApi
 * @since   1.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Core\RestApi;

use WP_REST_Server;
use WP_REST_Request;
use WP_REST_Response;
use WP_Error;
use ProRank\SEO\Plugin;

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


/**
 * Cache controller class
 */


class CacheController extends BaseController {
    
    /**
     * Rest base
     *
     * @var string
     */
    protected $rest_base = 'cache';
    
    /**
     * Register routes
     *
     * @return void
     */
    public function register_routes(): void {
        // Clear cache endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/clear',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'clear_cache'],
                    'permission_callback' => [$this, 'check_permission'],
                    'args'                => [
                        'type' => [
                            'required'          => false,
                            'type'              => 'string',
                            'default'           => 'all',
                            'enum'              => ['all', 'page', 'post', 'edge'],
                            'sanitize_callback' => 'sanitize_key',
                        ],
                        'id' => [
                            'required'          => false,
                            'type'              => 'integer',
                            'sanitize_callback' => 'absint',
                        ],
                    ],
                ],
            ]
        );
        
        // Get cache status endpoint
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/status',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_cache_status'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );
        
        // Get cache statistics endpoint (NEW for 2025)
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/stats',
            [
                [
                    'methods'             => WP_REST_Server::READABLE,
                    'callback'            => [$this, 'get_cache_stats'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );
        
        // Test speed endpoint (NEW for 2025)
        register_rest_route(
            $this->namespace,
            '/' . $this->rest_base . '/test-speed',
            [
                [
                    'methods'             => WP_REST_Server::CREATABLE,
                    'callback'            => [$this, 'test_speed'],
                    'permission_callback' => [$this, 'check_permission'],
                ],
            ]
        );
        
    }
    
    /**
     * Clear cache
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function clear_cache(WP_REST_Request $request) {
        // Sanitize inputs (defense in depth with schema sanitization)
        $type = sanitize_key($request->get_param('type'));
        $id = absint($request->get_param('id'));

        try {
            $plugin = Plugin::get_instance();
            $module_manager = $plugin->modules();
            
            // Check available cache modules
            $cache_module = $module_manager->get_module('cache');
            $modern_cache = $module_manager->get_module('modern_cache');
            if (!$cache_module && !$modern_cache) {
                return $this->get_error_response(
                    __('Cache module not found', 'prorank-seo'),
                    404
                );
            }
            
            // Clear cache based on type
            switch ($type) {
                case 'post':
                case 'page':
                    if (!$id) {
                        return $this->get_error_response(
                            __('Post ID required for single post cache clear', 'prorank-seo'),
                            400
                        );
                    }
                    $cleared = false;
                    if ($modern_cache && method_exists($modern_cache, 'smart_cache_invalidation')) {
                        $modern_cache->smart_cache_invalidation($id);
                        $cleared = true;
                    }
                    if ($cache_module && method_exists($cache_module, 'clear_post_cache')) {
                        $cache_module->clear_post_cache($id);
                        $cleared = true;
                    }
                    if (!$cleared) {
                        return $this->get_error_response(
                            __('Unable to clear cache for this post.', 'prorank-seo'),
                            500
                        );
                    }
                    $message = sprintf(
                        /* translators: %d: post ID */
                        __('Cache cleared for post %1$d', 'prorank-seo'),
                        $id
                    );
                    break;
                    
                case 'all':
                default:
                    if ($modern_cache && method_exists($modern_cache, 'clear_all_cache')) {
                        $modern_cache->clear_all_cache();
                    }
                    if ($cache_module && method_exists($cache_module, 'clear_all_cache')) {
                        $cache_module->clear_all_cache();
                    }
                    $message = __('All cache cleared successfully', 'prorank-seo');
                    break;
            }
            
            // Trigger action after cache clear
            do_action('prorank_seo_cache_cleared', $type, $id);
            
            return rest_ensure_response([
                'success' => true,
                'message' => $message,
                'type' => $type,
                'id' => $id,
            ]);
            
        } catch (\Exception $e) {
            return $this->get_error_response(
/* translators: %1$: value */
                sprintf(__('Failed to clear cache: %1$s', 'prorank-seo'), $e->getMessage()),
                500
            );
        }
    }
    
    /**
     * Get cache status
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_cache_status(WP_REST_Request $request) {
        try {
            $cache_usage = $this->calculate_cache_usage($this->get_cache_directories());
            $status = [
                'enabled' => $this->is_cache_enabled_setting(),
                'size' => $cache_usage['size'],
                'files' => $cache_usage['files'],
                'last_cleared' => null,
                'preload_status' => null,
                'has_cache_files' => $cache_usage['files'] > 0,
            ];

            // Get last cleared time from option
            $status['last_cleared'] = get_option('prorank_cache_last_cleared', null);
            
            return rest_ensure_response($status);
            
        } catch (\Exception $e) {
            return $this->get_error_response(
/* translators: %1$: value */
                sprintf(__('Failed to get cache status: %1$s', 'prorank-seo'), $e->getMessage()),
                500
            );
        }
    }
    
    /**
     * Get cache statistics (NEW for 2025)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function get_cache_stats(WP_REST_Request $request) {
        try {
            $plugin = Plugin::get_instance();
            $module_manager = $plugin->modules();
            $local_stats = $this->get_local_cache_stats();
            $remote_stats = $this->get_remote_stats();
            
            // Try to get modern cache module first
            $cache_module = $module_manager->get_module('modern_cache');
            if (!$cache_module) {
                // Fall back to regular cache module
                $cache_module = $module_manager->get_module('cache');
            }
            
            if (!$cache_module) {
                // Return default stats if no cache module
                return rest_ensure_response([
                    'hit_rate' => 0,
                    'miss_rate' => 0,
                    'total_cached' => 0,
                    'cache_size' => '0 MB',
                    'bandwidth_saved' => '0 GB',
                    'avg_response_time' => '0ms',
                    'edge_locations' => 0,
                ]);
            }
            
            // Get stats from cache module
            $stats = [];
            if (method_exists($cache_module, 'get_cache_stats')) {
                $stats = $cache_module->get_cache_stats();
                $stats = $this->merge_cache_stats_sources((array) $stats, $local_stats, $remote_stats);
            } else {
                // Calculate basic stats if method doesn't exist
                $cache_dirs = $this->get_cache_directories();
                $cache_usage = $this->calculate_cache_usage($cache_dirs);
                $total_size = (int) $cache_usage['size'];
                $file_count = (int) $cache_usage['html_files'];
                
                // Merge local and remote stats for comprehensive metrics
                $stats = [
                    'hit_rate' => $local_stats['hit_rate'] ?? $remote_stats['hit_rate'] ?? 0,
                    'miss_rate' => $local_stats['miss_rate'] ?? $remote_stats['miss_rate'] ?? 0,
                    'total_cached' => $file_count,
                    'cache_size' => $this->format_bytes($total_size),
                    'bandwidth_saved' => $this->calculate_bandwidth_saved($total_size, $local_stats),
                    'avg_response_time' => $local_stats['avg_response_time'] ?? $remote_stats['avg_response_time'] ?? 'N/A',
                    'edge_locations' => $remote_stats['edge_locations'] ?? $this->get_tier_edge_locations(),
                    'cache_efficiency' => $this->calculate_cache_efficiency($local_stats),
                    'compression_ratio' => $this->calculate_compression_ratio_from_directories($cache_dirs),
                    'real_time_status' => $this->get_real_time_status(),
                ];
                $stats = $this->merge_cache_stats_sources($stats, $local_stats, $remote_stats);
            }
            
            return rest_ensure_response($stats);
        } catch (\Exception $e) {
            return $this->get_error_response(
                $e->getMessage(),
                500
            );
        }
    }
    
    /**
     * Test speed (NEW for 2025)
     *
     * @param WP_REST_Request $request Request object
     * @return WP_REST_Response|WP_Error
     */
    public function test_speed(WP_REST_Request $request) {
        try {
            $home_url = home_url('/');
            
            // Test without cache (add random param to bypass cache)
            $start_no_cache = microtime(true);
            $response_no_cache = wp_remote_get($home_url . '?nocache=' . time(), [
                'timeout' => 10,
                'sslverify' => true,
            ]);
            $time_no_cache = (microtime(true) - $start_no_cache) * 1000;
            
            // Test with cache (should hit cache if enabled)
            $start_cache = microtime(true);
            $response_cache = wp_remote_get($home_url, [
                'timeout' => 10,
                'sslverify' => true,
            ]);
            $time_cache = (microtime(true) - $start_cache) * 1000;
            
            // Check if cache header is present
            $headers = wp_remote_retrieve_headers($response_cache);
            $cache_hit = isset($headers['x-prorank-cache']) && $headers['x-prorank-cache'] === 'HIT';
            
            // Calculate improvement
            $improvement = 0;
            if ($time_no_cache > 0) {
                $improvement = round((($time_no_cache - $time_cache) / $time_no_cache) * 100, 2);
            }
            
            $result = [
                'cache_enabled' => $cache_hit,
                'time_without_cache' => round($time_no_cache, 2) . 'ms',
                'time_with_cache' => round($time_cache, 2) . 'ms',
                'improvement' => $improvement . '%',
                'compression' => $headers['x-prorank-compression'] ?? 'none',
                'recommendations' => [],
            ];
            
            // Add recommendations
            if (!$cache_hit) {
                $result['recommendations'][] = __('Enable page caching for better performance', 'prorank-seo');
            }
            
            if ($improvement < 50 && $cache_hit) {
                $result['recommendations'][] = __('Consider enabling edge caching for better global performance', 'prorank-seo');
            }
            
            if (!isset($headers['content-encoding']) || strpos($headers['content-encoding'], 'br') === false) {
/* translators: %1$: value */
                $result['recommendations'][] = __('Enable Brotli compression for 20-30%1$ better compression', 'prorank-seo');
            }
            
            return rest_ensure_response($result);
        } catch (\Exception $e) {
            return $this->get_error_response(
                $e->getMessage(),
                500
            );
        }
    }
    
    /**
     * Format bytes to human readable
     *
     * @param int $bytes Bytes to format
     * @return string
     */
    private function format_bytes(int $bytes): string {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $i = 0;
        
        while ($bytes >= 1024 && $i < count($units) - 1) {
            $bytes /= 1024;
            $i++;
        }
        
        return round($bytes, 2) . ' ' . $units[$i];
    }

    /**
     * Resolve cache directories across legacy and modern cache layouts.
     *
     * @return array<int, string>
     */
    private function get_cache_directories(): array {
        $dirs = [
            WP_CONTENT_DIR . '/cache/prorank-seo', // Legacy cache path
        ];

        $upload_dir = wp_get_upload_dir();
        if (!empty($upload_dir['basedir']) && is_string($upload_dir['basedir'])) {
            // Modern cache writes under uploads/prorank-cache/*
            $dirs[] = trailingslashit($upload_dir['basedir']) . 'prorank-cache';
        }

        $existing = [];
        foreach ($dirs as $dir) {
            $normalized = untrailingslashit((string) $dir);
            if ($normalized !== '' && is_dir($normalized)) {
                $existing[] = $normalized;
            }
        }

        return array_values(array_unique($existing));
    }

    /**
     * Calculate aggregate cache usage across directories.
     *
     * @param array<int, string> $dirs Cache directories.
     * @return array<string, int>
     */
    private function calculate_cache_usage(array $dirs): array {
        $usage = [
            'size' => 0,
            'files' => 0,
            'html_files' => 0,
            'compressed_files' => 0,
            'compressed_size' => 0,
            'html_size' => 0,
        ];

        foreach ($dirs as $dir) {
            if (!is_dir($dir)) {
                continue;
            }

            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($dir, \RecursiveDirectoryIterator::SKIP_DOTS),
                \RecursiveIteratorIterator::LEAVES_ONLY
            );

            foreach ($iterator as $file) {
                if (!$file->isFile()) {
                    continue;
                }

                $ext = strtolower((string) $file->getExtension());
                $size = (int) $file->getSize();

                $usage['size'] += $size;
                $usage['files']++;

                if ($ext === 'html') {
                    $usage['html_files']++;
                    $usage['html_size'] += $size;
                } elseif ($ext === 'gz' || $ext === 'br') {
                    $usage['compressed_files']++;
                    $usage['compressed_size'] += $size;
                }
            }
        }

        return $usage;
    }

    /**
     * Determine whether page cache is enabled from current settings.
     */
    private function is_cache_enabled_setting(): bool {
        $settings = get_option('prorank_cache_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        return !empty($settings['cache_enabled'])
            || !empty($settings['enabled'])
            || !empty(get_option('prorank_cache_enabled', false));
    }

    /**
     * Determine whether an actual edge/cache network is configured.
     *
     * Do not treat license-tier capacity as active edge deployment. The stats UI
     * should only show edge locations when a CDN/edge layer is really enabled.
     */
    private function is_edge_network_active(): bool {
        $settings = get_option('prorank_cache_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $provider = sanitize_key((string) ($settings['cdn_provider'] ?? 'none'));
        if ($provider !== '' && $provider !== 'none') {
            return true;
        }

        return !empty($settings['edge_cache_enabled']) || !empty(get_option('prorank_edge_enabled', false));
    }
    
    /**
     * Get remote stats from ProRank.io
     *
     * @return array
     */
    private function get_remote_stats(): array {
        try {
            $response = wp_remote_get('https://prorank.io/api/v1/cache/simple', [
                'timeout' => 3,
                'headers' => [
                    'X-Site-Url' => get_site_url(),
                ],
            ]);
            
            if (!is_wp_error($response)) {
                $body = wp_remote_retrieve_body($response);
                return json_decode($body, true) ?: [];
            }
        } catch (\Exception $e) {
            prorank_log('ProRank cache stats error: ' . $e->getMessage());
        }
        
        return [];
    }
    
    /**
     * Get local cache statistics
     *
     * @return array
     */
    private function get_local_cache_stats(): array {
        // Get stats from WordPress transients (updated on each cache hit/miss)
        $stats = get_transient('prorank_cache_stats');
        $defaults = [
            'hits' => 0,
            'misses' => 0,
            'bytes_served' => 0,
            'response_times' => [],
            'last_updated' => time(),
        ];
        
        if ($stats === false) {
            // Initialize stats if not exists
            $stats = $defaults;
            set_transient('prorank_cache_stats', $stats, HOUR_IN_SECONDS);
        } else {
            // Backfill legacy/incomplete transient payloads.
            if (!is_array($stats)) {
                $stats = [];
            }
            $stats = wp_parse_args($stats, $defaults);
            if (!is_array($stats['response_times'])) {
                $stats['response_times'] = [];
            }
        }
        
        $hits = (int) $stats['hits'];
        $misses = (int) $stats['misses'];
        $bytes_served = (int) $stats['bytes_served'];
        $total = $hits + $misses;
        $hit_rate = $total > 0 ? round(($hits / $total) * 100, 2) : 0;
        
        // Calculate average response time
        $avg_response = 0;
        if (!empty($stats['response_times'])) {
            $avg_response = round(array_sum($stats['response_times']) / count($stats['response_times']), 2);
        }
        
        return [
            'hit_rate' => $hit_rate,
            'miss_rate' => 100 - $hit_rate,
            'total_hits' => $hits,
            'total_misses' => $misses,
            'bytes_served' => $bytes_served,
            'avg_response_time' => $avg_response > 0 ? $avg_response . 'ms' : null,
        ];
    }

    /**
     * Merge runtime cache stats into module/remote payload.
     *
     * Some hosts serve cached HTML directly (drop-in/web server path), which can
     * leave module file counters stale while transient counters are fresh.
     *
     * @param array $stats       Base stats payload
     * @param array $local_stats Runtime local stats
     * @param array $remote_stats Remote edge stats
     * @return array
     */
    private function merge_cache_stats_sources(array $stats, array $local_stats, array $remote_stats): array {
        $module_hits = (int) ($stats['hits'] ?? 0);
        $module_misses = (int) ($stats['misses'] ?? 0);
        $module_total = $module_hits + $module_misses;

        $local_hits = (int) ($local_stats['total_hits'] ?? 0);
        $local_misses = (int) ($local_stats['total_misses'] ?? 0);
        $local_total = $local_hits + $local_misses;

        // Prefer local runtime metrics when module counters are missing/stale.
        $prefer_local_runtime = $local_total > 0 && (
            $module_total === 0
            || $module_total < 50
            || $local_total > ($module_total * 20)
        );

        if ($prefer_local_runtime || !isset($stats['hit_rate'])) {
            $stats['hit_rate'] = (float) ($local_stats['hit_rate'] ?? $stats['hit_rate'] ?? 0);
        }
        if ($prefer_local_runtime || !isset($stats['miss_rate'])) {
            $stats['miss_rate'] = (float) ($local_stats['miss_rate'] ?? $stats['miss_rate'] ?? 0);
        }
        if ($prefer_local_runtime || empty($stats['avg_response_time']) || $stats['avg_response_time'] === 'N/A') {
            $stats['avg_response_time'] = $local_stats['avg_response_time'] ?? ($stats['avg_response_time'] ?? null);
        }

        if ($prefer_local_runtime && $local_total > 0 && ($module_total === 0 || $local_total > ($module_total * 20))) {
            $stats['hits'] = $local_hits;
            $stats['misses'] = $local_misses;
        }

        $resolved_hits = isset($stats['hits']) ? max(0, (int) $stats['hits']) : null;
        $resolved_misses = isset($stats['misses']) ? max(0, (int) $stats['misses']) : null;
        if ($resolved_hits !== null || $resolved_misses !== null) {
            $resolved_hits = $resolved_hits ?? 0;
            $resolved_misses = $resolved_misses ?? 0;
            $resolved_total = $resolved_hits + $resolved_misses;
            if ($resolved_total > 0) {
                $stats['hit_rate'] = round(($resolved_hits / $resolved_total) * 100, 2);
                $stats['miss_rate'] = round(($resolved_misses / $resolved_total) * 100, 2);
            }
        }

        if (isset($stats['bytes_saved']) && is_numeric($stats['bytes_saved'])) {
            $stats['bandwidth_saved'] = $this->format_bytes((int) $stats['bytes_saved']);
        } elseif (
            ($prefer_local_runtime || empty($stats['bandwidth_saved']) || $stats['bandwidth_saved'] === '0 B')
            && isset($local_stats['bytes_served'])
        ) {
            $stats['bandwidth_saved'] = $this->format_bytes((int) $local_stats['bytes_served']);
        }

        if (!$this->is_edge_network_active()) {
            $stats['edge_locations'] = 0;
        } elseif (empty($stats['edge_locations']) || (int) $stats['edge_locations'] === 0) {
            $stats['edge_locations'] = $remote_stats['edge_locations'] ?? $this->get_tier_edge_locations();
        }

        return $stats;
    }
    
    /**
     * Calculate bandwidth saved
     *
     * @param int   $cache_size Total cache size
     * @param array $stats      Local stats
     * @return string
     */
    private function calculate_bandwidth_saved(int $cache_size, array $stats): string {
        // Calculate based on cache hits and average page size
        $hits = $stats['total_hits'] ?? 0;
        $avg_page_size = $cache_size > 0 && $stats['total_hits'] > 0 
            ? $cache_size / max(1, $stats['total_hits']) 
            : 50000; // 50KB average
        
        $bandwidth_saved = $hits * $avg_page_size * 10; // Multiply by 10 for network overhead
        
        return $this->format_bytes($bandwidth_saved);
    }
    
    /**
     * Calculate cache efficiency
     *
     * @param array $stats Local stats
     * @return string
     */
    private function calculate_cache_efficiency(array $stats): string {
        $hit_rate = $stats['hit_rate'] ?? 0;
        
        if ($hit_rate >= 90) {
            return 'Excellent';
        } elseif ($hit_rate >= 70) {
            return 'Good';
        } elseif ($hit_rate >= 50) {
            return 'Fair';
        } else {
            return 'Building';
        }
    }
    
    /**
     * Calculate compression ratio
     *
     * @param string $cache_dir Cache directory
     * @return string
     */
    private function calculate_compression_ratio(string $cache_dir): string {
        $total_original = 0;
        $total_compressed = 0;
        
        if (is_dir($cache_dir)) {
            $iterator = new \RecursiveIteratorIterator(
                new \RecursiveDirectoryIterator($cache_dir, \RecursiveDirectoryIterator::SKIP_DOTS),
                \RecursiveIteratorIterator::LEAVES_ONLY
            );
            
            foreach ($iterator as $file) {
                if ($file->isFile()) {
                    $ext = $file->getExtension();
                    if ($ext === 'html') {
                        $total_original += $file->getSize();
                    } elseif ($ext === 'gz' || $ext === 'br') {
                        $total_compressed += $file->getSize();
                    }
                }
            }
        }
        
        if ($total_original > 0 && $total_compressed > 0) {
            $ratio = round((1 - ($total_compressed / $total_original)) * 100, 2);
            return $ratio . '%';
        }
        
        return 'N/A';
    }

    /**
     * Calculate compression ratio across multiple cache directories.
     *
     * @param array<int, string> $dirs Cache directories.
     */
    private function calculate_compression_ratio_from_directories(array $dirs): string {
        $usage = $this->calculate_cache_usage($dirs);
        $total_original = (int) ($usage['html_size'] ?? 0);
        $total_compressed = (int) ($usage['compressed_size'] ?? 0);

        if ($total_original > 0 && $total_compressed > 0) {
            $ratio = round((1 - ($total_compressed / $total_original)) * 100, 2);
            return $ratio . '%';
        }

        return 'N/A';
    }
    
    /**
     * Get real-time cache status
     *
     * @return array
     */
    private function get_real_time_status(): array {
        $settings = get_option('prorank_cache_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        return [
            'active' => $this->is_cache_enabled_setting(),
            'preloading' => !empty($settings['preload_enabled']) || !empty(get_option('prorank_preload_running', false)),
            'last_cleared' => get_option('prorank_cache_last_cleared', null),
            'optimization_score' => $this->calculate_optimization_score(),
        ];
    }
    
    /**
     * Calculate optimization score
     *
     * @return int
     */
    private function calculate_optimization_score(): int {
        $settings = get_option('prorank_cache_settings', []);
        if (!is_array($settings)) {
            $settings = [];
        }

        $score = 0;
        
        // Check various optimization settings
        if (!empty($settings['cache_enabled']) || !empty($settings['enabled']) || get_option('prorank_cache_enabled')) $score += 20;
        if (get_option('prorank_gzip_enabled')) $score += 15;
        if (get_option('prorank_brotli_enabled')) $score += 20;
        if (!empty($settings['preload_enabled']) || get_option('prorank_preload_enabled')) $score += 15;
        if (get_option('prorank_edge_enabled')) $score += 20;
        if (get_option('prorank_minify_enabled')) $score += 10;
        
        return min(100, $score);
    }
    
    /**
     * Get tier edge locations
     *
     * @return int
     */
    private function get_tier_edge_locations(): int {
        $plugin = Plugin::get_instance();
        $license_manager = $plugin->license();
        
        if (!$license_manager) {
            return 0;
        }
        
        $tier = $license_manager->get_tier();
        
        $locations = [
            'free' => 0,
            'basic' => 0,
            'pro' => 50,
            'business' => 150,
            'agency' => 275,
        ];
        
        return $locations[$tier] ?? 0;
    }
}
