<?php

/**
 * Advanced Cache Drop-In Generator
 *
 * Creates and manages the advanced-cache.php drop-in file for early cache interception.
 * Intercepts requests BEFORE WordPress loads for sub-50ms responses.
 *
 * @package ProRank\SEO\Cache
 * @since   2.0.0
 */

declare(strict_types=1);

namespace ProRank\SEO\Cache;

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


/**
 * Advanced Cache manager
 */


class AdvancedCache {

    /**
     * Path to advanced-cache.php drop-in
     *
     * @var string
     */
    private const DROP_IN_PATH = WP_CONTENT_DIR . '/advanced-cache.php';

    /**
     * Cache directory — must match ModernCacheModule::$cache_dir
     * ModernCacheModule uses: wp_upload_dir()['basedir'] . '/prorank-cache/html'
     * Default wp_upload_dir basedir = WP_CONTENT_DIR . '/uploads'
     *
     * @var string
     */
    private const CACHE_DIR = WP_CONTENT_DIR . '/uploads/prorank-cache/html';

    /**
     * Install the advanced-cache.php drop-in
     *
     * @return bool True on success
     */
    public static function install(): bool {
        // Check if we can write to wp-content
        if (!wp_is_writable(WP_CONTENT_DIR)) {
            prorank_log('[ProRank SEO] Cannot install advanced-cache.php: wp-content is not writable');
            return false;
        }

        // Check if another plugin's advanced-cache.php exists
        if (file_exists(self::DROP_IN_PATH)) {
            $content = file_get_contents(self::DROP_IN_PATH);
            if ($content !== false && trim($content) !== '' && strpos($content, 'ProRank SEO') === false) {
                // Another plugin owns this file
                prorank_log('[ProRank SEO] Another plugin owns advanced-cache.php, skipping installation');
                return false;
            }
        }

        // Generate and write the drop-in
        // The raw-analysis bypass secret is sourced from the existing
        // prorank_analyze_raw_secret option (managed elsewhere); a wp-config
        // PRORANK_ANALYZE_RAW_SECRET constant overrides it at request time.
        // Empty secret => bypass disabled in the drop-in.
        $raw_secret = (string) get_option('prorank_analyze_raw_secret', '');
        $drop_in_content = self::generate_drop_in(
            self::should_vary_cache_by_accept(),
            $raw_secret,
            self::should_separate_mobile_cache()
        );
        $result = file_put_contents(self::DROP_IN_PATH, $drop_in_content);

        if ($result === false) {
            prorank_log('[ProRank SEO] Failed to write advanced-cache.php');
            return false;
        }

        // Set WP_CACHE constant in wp-config.php
        self::set_wp_cache_constant(true);

        // Create cache directory
        if (!is_dir(self::CACHE_DIR)) {
            wp_mkdir_p(self::CACHE_DIR);
        }

        prorank_log('[ProRank SEO] advanced-cache.php installed successfully');
        return true;
    }

    /**
     * Uninstall the advanced-cache.php drop-in
     *
     * @return bool True on success
     */
    public static function uninstall(): bool {
        // Only remove if it's ours
        if (file_exists(self::DROP_IN_PATH)) {
            $content = file_get_contents(self::DROP_IN_PATH);
            if (strpos($content, 'ProRank SEO') !== false) {
                \ProRank\SEO\Core\FilesystemHelper::delete_file(self::DROP_IN_PATH);
                prorank_log('[ProRank SEO] advanced-cache.php removed');
            }
        }

        // Remove WP_CACHE constant
        self::set_wp_cache_constant(false);

        return true;
    }

    /**
     * Check if advanced-cache.php is installed and owned by ProRank
     *
     * @return bool
     */
    public static function is_installed(): bool {
        if (!file_exists(self::DROP_IN_PATH)) {
            return false;
        }

        $content = file_get_contents(self::DROP_IN_PATH);
        return strpos($content, 'ProRank SEO') !== false;
    }

    /**
     * Generate the advanced-cache.php content
     *
     * IMPORTANT: The generated code runs BEFORE WordPress loads, so it must:
     * - Use only native PHP functions (no wp_date, no WP classes, no autoloader)
     * - Match the path normalization logic in ModernCacheModule::get_cache_file_path()
     * - Use the same cache directory as ModernCacheModule (uploads/prorank-cache/html)
     *
     * @return string
     */
    private static function generate_drop_in(bool $vary_by_accept = false, string $raw_secret = '', bool $mobile_separate = false): string {
        $template = <<<'PHP'
<?php
/**
 * ProRank SEO Advanced Cache
 *
 * This file is generated by ProRank SEO. Do not edit manually.
 * It intercepts requests BEFORE WordPress loads for maximum performance.
 *
 * IMPORTANT: This runs before WP is loaded, so only native PHP functions are available.
 * No wp_date(), no WP filesystem, no autoloaded classes.
 *
 * @package ProRank\SEO
 * @since   2.0.0
 */

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

$prorank_server_value = static function (string $key, string $default = ''): string {
    if (!isset($_SERVER[$key]) || !is_scalar($_SERVER[$key])) {
        return $default;
    }

    return trim((string) $_SERVER[$key]);
};

// Only run for GET/HEAD requests
$request_method = strtoupper($prorank_server_value('REQUEST_METHOD'));
$is_head = ($request_method === 'HEAD');
if (!in_array($request_method, ['GET', 'HEAD'], true)) {
    return;
}

// Skip if explicitly disabled
if (defined('PRORANK_CACHE_DISABLED') && PRORANK_CACHE_DISABLED) {
    return;
}

// Raw Cloud UCSS analysis bypass. These probes must reach WordPress so the
// frontend optimization bypass can emit unoptimized source HTML/CSS. The
// bypass is gated on a per-site secret: an unauthenticated header alone must
// never be able to skip the page cache. If no secret is configured the
// bypass is disabled entirely.
$prorank_raw_header = $prorank_server_value('HTTP_X_PRORANK_ANALYZE_RAW');
if ($prorank_raw_header !== '') {
    $prorank_raw_secret = defined('PRORANK_ANALYZE_RAW_SECRET')
        ? (string) PRORANK_ANALYZE_RAW_SECRET
        : '__PRORANK_ANALYZE_RAW_SECRET__';
    if ($prorank_raw_secret !== ''
        && hash_equals($prorank_raw_secret, $prorank_raw_header)) {
        return;
    }
}

// Skip for logged in users (check cookies)
$logged_in_cookies = ['wordpress_logged_in_', 'wp-postpass_', 'comment_author_'];
foreach ($_COOKIE as $name => $value) {
    foreach ($logged_in_cookies as $prefix) {
        if (strpos($name, $prefix) === 0) {
            return;
        }
    }
}

// Skip any request with query strings
if (!empty($_GET)) {
    return;
}

// Get cache file path - must match ModernCacheModule::get_cache_file_path() exactly
$host = preg_replace('/[^A-Za-z0-9\\.\\-:\\[\\]]/', '', $prorank_server_value('HTTP_HOST'));
$uri  = $prorank_server_value('REQUEST_URI', '/');
$path = parse_url($uri, PHP_URL_PATH) ?: '/';

// Normalize the path - replicate str_replace from ModernCacheModule
$path = trim($path, '/');
if (empty($path)) {
    $path = 'index';
} else {
    $path = str_replace(['/', '\\', '?', '&', '='], '-', $path);
}

// Optional cache variation by device. Must run before the Accept suffix to
// match ModernCacheModule::get_cache_file_path().
$prorank_mobile_separate = __PRORANK_MOBILE_SEPARATE__;
if ($prorank_mobile_separate) {
    $user_agent = $prorank_server_value('HTTP_USER_AGENT');
    foreach (['Mobile', 'Android', 'iPhone', 'iPad', 'Windows Phone'] as $mobile_agent) {
        if (stripos($user_agent, $mobile_agent) !== false) {
            $path .= '-mobile';
            break;
        }
    }
}

// Optional cache variation by image format capabilities.
// Keep this in sync with ModernCacheModule cache-key logic.
$prorank_vary_accept = __PRORANK_VARY_ACCEPT__;
if ($prorank_vary_accept) {
    $accept = strtolower($prorank_server_value('HTTP_ACCEPT'));
    if (strpos($accept, 'image/jxl') !== false) {
        $path .= '-fmt-jxl';
    } elseif (strpos($accept, 'image/avif') !== false) {
        $path .= '-fmt-avif';
    } elseif (strpos($accept, 'image/webp') !== false) {
        $path .= '-fmt-webp';
    } else {
        $path .= '-fmt-legacy';
    }
}

// Build cache file path
// ModernCacheModule uses wp_upload_dir()/prorank-cache/html/ which resolves to
// wp-content/uploads/prorank-cache/html/ by default. We hardcode that path here
// since WordPress functions are not available. If the uploads dir has been relocated,
// define PRORANK_CACHE_DIR in wp-config.php to override.
if (defined('PRORANK_CACHE_DIR')) {
    $cache_dir = PRORANK_CACHE_DIR;
} else {
    $cache_dir = WP_CONTENT_DIR . '/uploads/prorank-cache/html';
}

$cache_file = $cache_dir . '/' . $host . '/' . $path . '.html';

// Check if cache file exists
if (!file_exists($cache_file)) {
    return;
}

// Check cache lifetime (default 7 days; content changes purge targeted URLs).
$cache_lifetime = defined('PRORANK_CACHE_LIFETIME') ? (int) PRORANK_CACHE_LIFETIME : 604800;
$file_time = filemtime($cache_file);
if ($file_time === false || (time() - $file_time) > $cache_lifetime) {
    return;
}

// Determine best compression format from available files + client Accept-Encoding
$accept_encoding = strtolower($prorank_server_value('HTTP_ACCEPT_ENCODING'));
$compression = 'none';
$serve_file = $cache_file;

if (strpos($accept_encoding, 'br') !== false && file_exists($cache_file . '.br')) {
    $compression = 'br';
    $serve_file = $cache_file . '.br';
} elseif (strpos($accept_encoding, 'gzip') !== false && file_exists($cache_file . '.gz')) {
    $compression = 'gzip';
    $serve_file = $cache_file . '.gz';
}

// Send response headers
header('X-ProRank-Cache: HIT');
header('X-ProRank-Cache-Type: drop-in');
header('X-ProRank-Cache-Time: ' . gmdate('Y-m-d H:i:s', $file_time));
header('Cache-Control: no-cache, must-revalidate');
header('Content-Type: text/html; charset=UTF-8');
header('Vary: ' . ($prorank_vary_accept ? 'Accept-Encoding, Accept' : 'Accept-Encoding'));

// Server timing
$start_time = isset($_SERVER['REQUEST_TIME_FLOAT']) ? (float) $_SERVER['REQUEST_TIME_FLOAT'] : microtime(true);
$response_time = round((microtime(true) - $start_time) * 1000, 2);
header('Server-Timing: cache;desc="ProRank Drop-In HIT";dur=' . $response_time);

if ($compression === 'br') {
    header('Content-Encoding: br');
} elseif ($compression === 'gzip') {
    header('Content-Encoding: gzip');
}

// Output cached content (skip body for HEAD requests)
if (!$is_head) {
    readfile($serve_file);
}

// Track cache hit stats (lightweight, no WordPress needed).
// Uses a separate JSON file that ModernCacheModule merges on next WP load.
$dropin_stats_file = dirname($cache_dir) . '/dropin-hits.json';
$ds = [];
if (file_exists($dropin_stats_file)) {
    $raw = @file_get_contents($dropin_stats_file);
    if ($raw !== false && $raw !== '') {
        $ds = @json_decode($raw, true) ?: [];
    }
}
$ds['hits'] = ($ds['hits'] ?? 0) + 1;
$ds['bytes_saved'] = ($ds['bytes_saved'] ?? 0) + (int) @filesize($serve_file);
$ds['last_hit'] = time();
// Keep last 50 response times for average calculation.
$rt = $ds['response_times'] ?? [];
$rt[] = $response_time;
if (count($rt) > 50) {
    $rt = array_slice($rt, -50);
}
$ds['response_times'] = $rt;
@file_put_contents($dropin_stats_file, json_encode($ds), LOCK_EX);

// Exit before WordPress loads - this is the key performance optimization
exit;
PHP;
        return str_replace(
            ['__PRORANK_VARY_ACCEPT__', '__PRORANK_ANALYZE_RAW_SECRET__', '__PRORANK_MOBILE_SEPARATE__'],
            [$vary_by_accept ? 'true' : 'false', addslashes($raw_secret), $mobile_separate ? 'true' : 'false'],
            $template
        );
    }

    /**
     * Set or remove WP_CACHE constant in wp-config.php
     *
     * @param bool $enable True to enable, false to disable
     * @return bool
     */
    private static function set_wp_cache_constant(bool $enable): bool {
        $config_file = ABSPATH . 'wp-config.php';

        // Check if wp-config.php is writable
        if (!wp_is_writable($config_file)) {
            prorank_log('[ProRank SEO] wp-config.php is not writable');
            return false;
        }

        $content = file_get_contents($config_file);
        if ($content === false) {
            return false;
        }

        // Check if WP_CACHE is already defined
        $pattern = "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*(true|false|1|0)\s*\)\s*;/i";

        if ($enable) {
            // Add or update WP_CACHE
            $replacement = "define('WP_CACHE', true); // Added by ProRank SEO";

            if (preg_match($pattern, $content)) {
                // Update existing
                $content = preg_replace($pattern, $replacement, $content);
            } else {
                // Add after opening PHP tag or after any existing defines
                $content = preg_replace(
                    "/(<\?php)/i",
                    "$1\n" . $replacement,
                    $content,
                    1
                );
            }
        } else {
            // Remove our WP_CACHE line
            $content = preg_replace(
                "/define\s*\(\s*['\"]WP_CACHE['\"]\s*,\s*true\s*\)\s*;\s*\/\/\s*Added by ProRank SEO\n?/i",
                '',
                $content
            );
        }

        return file_put_contents($config_file, $content) !== false;
    }

    /**
     * Regenerate the advanced-cache.php with updated settings
     *
     * @param array $settings Cache settings
     * @return bool
     */
    public static function regenerate(array $settings = []): bool {
        if (!self::is_installed()) {
            return self::install();
        }

        // For now, just regenerate the standard drop-in
        // Future: incorporate settings into the generated file
        return self::install();
    }

    /**
     * Whether cache keys must vary by Accept header for image delivery.
     *
     * @return bool
     */
    private static function should_vary_cache_by_accept(): bool {
        $settings = get_option('prorank_image_optimization_settings', []);
        if (!is_array($settings)) {
            return false;
        }

        $delivery_method = (string) ($settings['delivery_method'] ?? '');
        if ($delivery_method !== 'accept_header') {
            return false;
        }

        return !empty($settings['jxl_enabled'])
            || !empty($settings['avif_enabled'])
            || !empty($settings['webp_enabled']);
    }

    /**
     * Whether cache keys must vary by mobile user agent.
     *
     * @return bool
     */
    private static function should_separate_mobile_cache(): bool {
        $settings = get_option('prorank_cache_settings', []);
        if (!is_array($settings)) {
            return false;
        }

        return !empty($settings['mobile_separate']);
    }

    /**
     * Get cache status information
     *
     * @return array
     */
    public static function get_status(): array {
        return [
            'drop_in_installed' => self::is_installed(),
            'drop_in_path' => self::DROP_IN_PATH,
            'cache_dir' => self::CACHE_DIR,
            'cache_dir_exists' => is_dir(self::CACHE_DIR),
            'cache_dir_writable' => wp_is_writable(self::CACHE_DIR),
            'wp_cache_enabled' => defined('WP_CACHE') && WP_CACHE,
        ];
    }
}
