<?php

namespace TenWebOptimizer\WebPageCache;

use TenWebOptimizer\OptimizerUtils;

/*
 * Base class other (more-specific) classes inherit from.
 */
if (!defined('ABSPATH')) {
    exit;
}

/**
 * This class will work with wp hooks/filter. Will decide when to clear cache, enable/disable web caching, etc.
 *
 * */
class OptimizerWebPageCacheWP
{
    protected static $instance = null;

    public $wp_config_file_path = ABSPATH . '/wp-config.php';

    public $page_cache_config_dir = WP_CONTENT_DIR . '/10web-page-cache-config';

    public $wp_cache_define = "define( 'WP_CACHE', true );\ndefine( 'TWO_PLUGIN_DIR_CACHE', '" . TENWEB_SO_PLUGIN_DIR . "' );";

    public $advanced_cache_path = WP_CONTENT_DIR . '/advanced-cache.php';

    public function __construct()
    {
        if (isset($_GET['action']) && $_GET['action'] === 'two_clear_page_cache') {  // phpcs:ignore
            add_action('admin_init', [ $this, 'maybe_clear_page_cache' ]);
        }

        global $TwoSettings;

        if ($TwoSettings->get_settings('two_page_cache') === 'on' && get_option('show_on_front') === 'posts') {
            add_action('transition_post_status', [$this, 'transition_post_status'], 10, 3);
        }

        if ((!isset($_GET['action']) || $_GET['action'] != 'deactivate') // phpcs:ignore
            && (!isset($_GET['rest_route']) || $_GET['rest_route'] != '/tenweb_so/v1/set_modes')) { // phpcs:ignore
            add_action('update_option_two_settings', [ $this, 'enable_disable_page_cache' ], 10, 3);
        }

        add_filter('page_row_actions', [$this, 'post_row_actions'], 10, 2);
        add_filter('post_row_actions', [$this, 'post_row_actions'], 10, 2);

        if ($TwoSettings->get_settings('two_page_cache') === 'on' && $TwoSettings->get_settings('two_page_cache_user') === 'on') {
            if (is_user_logged_in()) {
                // phpcs:ignore WordPressVIPMinimum.Variables.RestrictedVariables.cache_constraints___COOKIE, WordPress.Security.ValidatedSanitizedInput.InputNotValidated
                $logged_in_cookie = sanitize_text_field($_COOKIE[LOGGED_IN_COOKIE]);
                // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
                setcookie('tenweb_so_page_cache_hash', md5($logged_in_cookie), time() + (int) $TwoSettings->get_settings('two_page_cache_life_time'), COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
            } else {
                if (isset($_COOKIE['tenweb_so_page_cache_hash'])) {
                    // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.cookies_setcookie
                    setcookie('tenweb_so_page_cache_hash', '', time() + (int) $TwoSettings->get_settings('two_page_cache_life_time'), COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true);
                }
            }
        }
    }

    public function delete_all_cache()
    {
        OptimizerWebPageCache::delete_all_cached_pages();
    }

    public function post_updated($post_id, $post_after, $post_before)
    {
        OptimizerWebPageCache::delete_cache_by_post_id($post_id);
    }

    public function enable_disable_page_cache($old_value, $value, $option)
    {
        $value = json_decode($value, true);
        $value = (isset($value[ 'two_page_cache' ])) ? $value['two_page_cache'] : '';

        if ($value === 'on') {
            $this->store_page_cache_configs();

            if (!file_exists(WP_CONTENT_DIR . '/advanced-cache.php')) {
                $this->enable_page_cache();
            } else {
                $file_content = file_get_contents(WP_CONTENT_DIR . '/advanced-cache.php');

                if (strpos($file_content, 'TENWEB_SO_ADVANCED_CACHE') === false) {
                    $this->enable_page_cache();
                } elseif (!defined('WP_CACHE') || !WP_CACHE || !defined('TWO_PLUGIN_DIR_CACHE')) {
                    $this->add_wp_cache_constant();
                }
            }
        } else {
            $this->disable_page_cache();
        }
    }

    public function enable_page_cache()
    {
        if ($this->add_wp_cache_constant() === false) {
            return false;
        }

        return copy(TENWEB_SO_PLUGIN_DIR . 'includes/WebPageCache/advanced-cache.php', $this->advanced_cache_path);
    }

    public function disable_page_cache()
    {
        if ($this->remove_wp_cache_constant() === false) {
            return false;
        }

        if ($this->delete_page_cache_configs() === false) {
            return false;
        }

        if (!file_exists($this->advanced_cache_path)) {
            return true;
        }

        return unlink($this->advanced_cache_path); // phpcs:ignore
    }

    public function add_wp_cache_constant()
    {
        // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_is_writable
        if (!is_writable($this->wp_config_file_path) || !is_readable($this->wp_config_file_path)) {
            return false;
        }

        // Save untouched version of the file
        $original_file_content = file_get_contents($this->wp_config_file_path); // phpcs:ignore

        if ($original_file_content === false) {
            error_log('10Web Speed Optimizer: Failed to read original wp-config.php file'); // phpcs:ignore

            return false;
        }

        $file_content = $original_file_content;

        // Remove our constants to clear the file in case if somebody removed the WP_CACHE define
        $file_content = preg_replace('/# BEGIN WP Cache by 10Web[\s\S]*# END WP Cache by 10Web/', '', $file_content);

        if ($file_content === null) {
            error_log('10Web Speed Optimizer: Failed to remove existing WP Cache constants'); // phpcs:ignore

            return false;
        }

        // Remove WP_CACHE defined by others
        $file_content = OptimizerUtils::delete_define('WP_CACHE', $file_content);

        if (strpos($file_content, '<?php') === false) {
            // Restore original file content
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to remove existing WP_CACHE define'); // phpcs:ignore

            return false;
        }

        // Add new defines
        $replacement = "<?php\n# BEGIN WP Cache by 10Web\n" . $this->wp_cache_define . "\n# END WP Cache by 10Web\n";
        $file_content = preg_replace('@<\?php\s*@i', $replacement, $file_content, 1);

        if ($file_content === null) {
            // Restore original file content
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to add new WP Cache constants'); // phpcs:ignore

            return false;
        }

        // Ensure essential WordPress constants are still present
        if (strpos($file_content, 'require_once') === false ||
            strpos($file_content, 'ABSPATH') === false) {
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Essential WordPress constants missing after modification'); // phpcs:ignore

            return false;
        }

        // Write to temporary file first for atomic operation
        $temp_file = $this->wp_config_file_path . '.tmp.' . uniqid();
        $write_result = file_put_contents($temp_file, $file_content); // phpcs:ignore

        if ($write_result === false) {
            // Clean up temp file and restore original content
            if (file_exists($temp_file)) {
                unlink($temp_file); // phpcs:ignore
            }
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to write modified content to temporary file'); // phpcs:ignore

            return false;
        }

        // Atomic rename operation
        $rename_result = rename($temp_file, $this->wp_config_file_path); // phpcs:ignore

        if (!$rename_result) {
            // Clean up temp file and restore original content
            unlink($temp_file); // phpcs:ignore
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to rename temporary file to wp-config.php'); // phpcs:ignore

            return false;
        }

        return true;
    }

    public function remove_wp_cache_constant()
    {
        // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.file_ops_is_writable
        if (!is_writable($this->wp_config_file_path) || !is_readable($this->wp_config_file_path)) {
            return false;
        }

        // Save untouched version of the file
        $original_file_content = file_get_contents($this->wp_config_file_path); // phpcs:ignore

        if ($original_file_content === false) {
            error_log('10Web Speed Optimizer: Failed to read original wp-config.php file'); // phpcs:ignore

            return false;
        }

        $file_content = $original_file_content;
        $file_content = preg_replace('/# BEGIN WP Cache by 10Web[\s\S]*# END WP Cache by 10Web/', '', $file_content);

        if ($file_content === null) {
            error_log('10Web Speed Optimizer: Failed to remove WP Cache constants'); // phpcs:ignore

            return false;
        }

        // Write to temporary file first for atomic operation
        $temp_file = $this->wp_config_file_path . '.tmp.' . uniqid();
        $write_result = file_put_contents($temp_file, $file_content); // phpcs:ignore

        if ($write_result === false) {
            // Clean up temp file and restore original content
            if (file_exists($temp_file)) {
                unlink($temp_file); // phpcs:ignore
            }
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to write modified content to temporary file'); // phpcs:ignore

            return false;
        }

        // Atomic rename operation
        $rename_result = rename($temp_file, $this->wp_config_file_path); // phpcs:ignore

        if (!$rename_result) {
            // Clean up temp file and restore original content
            unlink($temp_file); // phpcs:ignore
            file_put_contents($this->wp_config_file_path, $original_file_content); // phpcs:ignore
            error_log('10Web Speed Optimizer: Failed to rename temporary file to wp-config.php'); // phpcs:ignore

            return false;
        }

        return true;
    }

    public function store_page_cache_configs()
    {
        if (!is_dir($this->page_cache_config_dir) && !mkdir($concurrentDirectory = $this->page_cache_config_dir, 0777) && !is_dir($concurrentDirectory)) { // phpcs:ignore
            return false;
        }
        $configs = [];
        $TwoSettings = new \TenWebOptimizer\OptimizerSettings();
        $configs['two_settings'] = json_encode($TwoSettings->get_settings()); // phpcs:ignore

        return (bool) file_put_contents($this->page_cache_config_dir . '/config.json', json_encode($configs)); // phpcs:ignore
    }

    public function delete_page_cache_configs()
    {
        if (file_exists($this->page_cache_config_dir . '/config.json')) {
            return unlink($this->page_cache_config_dir . '/config.json'); // phpcs:ignore
        }

        return true;
    }

    public function maybe_clear_page_cache()
    {
        // Check user capabilities
        if (!current_user_can('manage_options')) {
            wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'tenweb-speed-optimizer'), 403);
        }

        // Verify nonce
        $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';

        if (!wp_verify_nonce($nonce, 'two_clear_page_cache')) {
            wp_die(esc_html__('Security check failed. Please try again.', 'tenweb-speed-optimizer'), 403);
        }

        if (!empty($_GET['permalink'])) {
            $permalink = sanitize_url($_GET['permalink']); // phpcs:ignore

            // Validate URL - reject if contains dangerous characters instead of trying to fix it
            if (!self::is_url_safe_for_cache_clear($permalink)) {
                status_header(400);
                wp_die(esc_html__('Invalid URL: contains dangerous characters.', 'tenweb-speed-optimizer'), 400);
            }

            // Validate that the cache directory path stays within allowed boundaries
            $cache_dir = OptimizerWebPageCache::get_cache_dir_for_page_from_url($permalink);
            $cache_dir = self::validate_cache_path($cache_dir);

            if ($cache_dir === false) {
                wp_die(esc_html__('Invalid cache path detected.', 'tenweb-speed-optimizer'), 403);
            }

            OptimizerUtils::clear_cloudflare_cache([$permalink]);
            OptimizerWebPageCache::delete_cache_by_url($permalink);
        }

        $redirect_to = (!empty($_SERVER['HTTP_REFERER'])) ? sanitize_text_field($_SERVER['HTTP_REFERER']) : '/wp-admin/edit.php';

        wp_safe_redirect($redirect_to);
        die;
    }

    /**
     * Validate URL for cache clearing - reject URLs with dangerous characters
     * Instead of trying to fix the URL, we reject it to prevent path traversal attacks
     *
     * @param string $url The URL to validate
     *
     * @return bool True if URL is safe, false if it contains dangerous characters
     */
    private static function is_url_safe_for_cache_clear($url)
    {
        if (empty($url)) {
            return false;
        }

        $parsed_url = wp_parse_url($url);

        // Must have host and path
        if (!isset($parsed_url['host']) || !isset($parsed_url['path'])) {
            return false;
        }

        $path = $parsed_url['path'];

        // Reject if contains directory traversal sequences
        if (strpos($path, '../') !== false || strpos($path, '..\\') !== false) {
            return false;
        }

        // Reject if contains null bytes
        if (strpos($path, "\0") !== false) {
            return false;
        }

        // Reject if path contains encoded directory traversal
        if (strpos($path, '%2e%2e%2f') !== false || strpos($path, '%2e%2e\\') !== false) {
            return false;
        }

        // URL is safe
        return true;
    }

    /**
     * Validate that cache path stays within TENWEB_SO_PAGE_CACHE_DIR
     *
     * @param string $cache_dir The cache directory path to validate
     *
     * @return string|false Returns normalized path if valid, false otherwise
     */
    private static function validate_cache_path($cache_dir)
    {
        if (empty($cache_dir)) {
            return false;
        }

        // Normalize paths for comparison
        $cache_dir = rtrim($cache_dir, '/') . '/';
        $allowed_dir = rtrim(TENWEB_SO_PAGE_CACHE_DIR, '/') . '/';

        // First check: ensure the path starts with the allowed directory (string comparison)
        // This works even if directories don't exist yet
        if (strpos($cache_dir, $allowed_dir) !== 0) {
            return false;
        }

        // Second check: use realpath if directory exists to resolve symlinks and normalize
        $real_cache_dir = realpath($cache_dir);
        $real_allowed_dir = realpath($allowed_dir);

        // If both paths exist, verify the resolved path is still within allowed directory
        if ($real_cache_dir !== false && $real_allowed_dir !== false) {
            if (strpos($real_cache_dir, $real_allowed_dir) !== 0) {
                return false;
            }

            return $real_cache_dir;
        }

        // If directory doesn't exist yet, return the normalized path if it passed string check
        // This allows validation of paths that will be created
        return $cache_dir;
    }

    public function post_row_actions($actions, $post)
    {
        global $TwoSettings;

        if ($TwoSettings->get_settings('two_page_cache') !== 'on') {
            return $actions;
        }

        $url = wp_nonce_url(admin_url('admin-post.php?action=two_clear_page_cache&permalink=' . get_permalink($post)), 'two_clear_page_cache');
        $actions['two_clear_page_cache'] = sprintf('<a href="%s">%s</a>', $url, __('Clear page cache', 'tenweb-speed-optimizer'));

        return $actions;
    }

    public function transition_post_status($new_status, $old_status, $post)
    {
        if ($post->post_type !== 'post') {
            return;
        }

        if ($new_status === $old_status) {
            return;
        }

        // if status changed from publish (e.g. to trash) or to publish (e.g. from draft to publish) clear page cache
        if ($new_status === 'publish' || $old_status === 'publish') {
            OptimizerUtils::clear_cloudflare_cache([get_home_url()]);
            OptimizerWebPageCache::delete_cache_by_url(get_home_url());
        }
    }

    public static function get_instance()
    {
        if (null == self::$instance) {
            self::$instance = new self();
        }

        return self::$instance;
    }
}
