<?php

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

class SEO_Fury_Htaccess {

    private $htaccess_path;

    public function __construct() {
        $this->htaccess_path = ABSPATH . '.htaccess';

        add_action('wp_ajax_seo_fury_save_htaccess', array($this, 'ajax_save'));
        add_action('wp_ajax_seo_fury_restore_htaccess', array($this, 'ajax_restore'));
    }

    /**
     * Get current .htaccess content
     */
    public function get_content() {
        if (!file_exists($this->htaccess_path)) {
            return '';
        }
        return file_get_contents($this->htaccess_path);
    }

    /**
     * Check if .htaccess is writable
     */
    public function is_writable() {
        if (file_exists($this->htaccess_path)) {
            return is_writable($this->htaccess_path); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
        }
        return is_writable(ABSPATH); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
    }

    /**
     * Check if running on Apache
     */
    public function is_apache() {
        if (function_exists('apache_get_version')) {
            return true;
        }
        $server = isset($_SERVER['SERVER_SOFTWARE']) ? sanitize_text_field(wp_unslash($_SERVER['SERVER_SOFTWARE'])) : '';
        return stripos($server, 'apache') !== false || stripos($server, 'litespeed') !== false;
    }

    /**
     * Get backup content
     */
    public function get_backup() {
        return get_option('seo_fury_htaccess_backup', '');
    }

    /**
     * Get backup timestamp
     */
    public function get_backup_time() {
        return get_option('seo_fury_htaccess_backup_time', '');
    }

    /**
     * Create backup of current .htaccess
     */
    private function create_backup() {
        $content = $this->get_content();
        update_option('seo_fury_htaccess_backup', $content);
        update_option('seo_fury_htaccess_backup_time', current_time('mysql'));
    }

    /**
     * Save .htaccess with safety check
     */
    public function save_content($new_content) {
        // Create backup first
        $this->create_backup();

        // Write new content
        $result = file_put_contents($this->htaccess_path, $new_content);
        if ($result === false) {
            return array(
                'success' => false,
                'message' => __('Failed to write .htaccess file', 'seo-fury'),
            );
        }

        // Safety check: make HTTP request to home URL
        $response = wp_remote_get(home_url('/'), array(
            'timeout'   => 10,
            'sslverify' => false,
            'headers'   => array('Cache-Control' => 'no-cache'),
        ));

        // A WP_Error means the loopback request never reached the server
        // (common on hosts that block self-requests) — NOT proof the site is
        // broken. Only a real HTTP 500 confirms the new rules broke the site.
        if (is_wp_error($response)) {
            return array(
                'success' => true,
                'message' => __('.htaccess saved. Note: the automatic safety check could not run on this host (the site could not reach itself). Please open your site in a new tab to confirm it loads — if anything broke, use "Restore Backup".', 'seo-fury'),
                'unverified' => true,
            );
        }

        $code = (int) wp_remote_retrieve_response_code($response);

        if ($code === 500) {
            // Site is broken - rollback immediately
            $backup = $this->get_backup();
            file_put_contents($this->htaccess_path, $backup);

            return array(
                'success'     => false,
                'message'     => __('The .htaccess changes caused a server error (HTTP 500). Changes have been automatically rolled back.', 'seo-fury'),
                'rolled_back' => true,
            );
        }

        return array(
            'success' => true,
            'message' => __('.htaccess saved successfully', 'seo-fury'),
        );
    }

    /**
     * Scan content for directives that could install a PHP backdoor or proxy
     * requests to an attacker-controlled server. Returns the first offending
     * line, or false if the content is clean.
     *
     * @param  string $content
     * @return string|false
     */
    private function find_dangerous_directive($content) {
        $dangerous_patterns = array(
            // PHP execution / code injection
            '/^\s*php_value\s+auto_(pre|ap)pend_file\b.*/mi',
            '/^\s*php_admin_(value|flag)\b.*/mi',
            '/^\s*php_(flag|value)\s+engine\b.*/mi',
            '/^\s*AddType\s+application\/x-httpd-php.*/mi',
            '/^\s*(AddHandler|SetHandler)\b[^\n]*php.*/mi',
            '/^\s*Action\b[^\n]*(php|cgi).*/mi',
            // Reverse-proxy backdoor (RewriteRule with the [P] proxy flag)
            '/^\s*RewriteRule[^\n]*\[[^\]\n]*\bP\b[^\]\n]*\].*/mi',
        );

        foreach ($dangerous_patterns as $pattern) {
            if (preg_match($pattern, $content, $m)) {
                return trim($m[0]);
            }
        }

        return false;
    }

    /**
     * Restore from backup
     */
    public function restore_backup() {
        $backup = $this->get_backup();
        if (empty($backup) && $backup !== '') {
            // backup exists but is empty string - that's still valid (empty .htaccess)
        }

        if ($backup === '' && !get_option('seo_fury_htaccess_backup_time', '')) {
            return array(
                'success' => false,
                'message' => __('No backup available', 'seo-fury'),
            );
        }

        $result = file_put_contents($this->htaccess_path, $backup);
        if ($result === false) {
            return array(
                'success' => false,
                'message' => __('Failed to restore .htaccess', 'seo-fury'),
            );
        }

        return array(
            'success' => true,
            'message' => __('.htaccess restored from backup', 'seo-fury'),
        );
    }

    /**
     * AJAX: Save .htaccess
     */
    public function ajax_save() {
        check_ajax_referer('seo_fury_htaccess_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('Permission denied', 'seo-fury')));
        }

        $content = isset($_POST['content']) ? wp_unslash($_POST['content']) : '';
        // Don't sanitize with sanitize_textarea_field - it strips needed special chars
        // .htaccess content can contain special characters that are valid Apache directives

        // Reject (rather than silently strip) dangerous directives that could
        // install a PHP backdoor or proxy traffic, so the admin sees exactly
        // why the save was blocked instead of getting a half-modified file.
        $dangerous = $this->find_dangerous_directive($content);
        if ($dangerous !== false) {
            wp_send_json_error(array(
                'message' => sprintf(
                    /* translators: %s: the offending .htaccess line */
                    __('Save blocked: this line could allow PHP code execution or request proxying and was rejected for security: %s', 'seo-fury'),
                    $dangerous
                ),
            ));
        }

        $result = $this->save_content($content);

        if ($result['success']) {
            wp_send_json_success(array(
                'message'     => $result['message'],
                'backup_time' => $this->get_backup_time(),
                'unverified'  => !empty($result['unverified']),
            ));
        } else {
            wp_send_json_error($result);
        }
    }

    /**
     * AJAX: Restore .htaccess from backup
     */
    public function ajax_restore() {
        check_ajax_referer('seo_fury_htaccess_nonce', 'nonce');

        if (!current_user_can('manage_options')) {
            wp_send_json_error(array('message' => __('Permission denied', 'seo-fury')));
        }

        $result = $this->restore_backup();

        if ($result['success']) {
            wp_send_json_success(array(
                'message' => $result['message'],
                'content' => $this->get_content(),
            ));
        } else {
            wp_send_json_error($result);
        }
    }

    /**
     * Get preset snippets
     */
    public static function get_presets() {
        return array(
            'browser_caching' => array(
                'name' => __('Browser Caching', 'seo-fury'),
                'desc' => __('Set Expires and Cache-Control headers for static assets', 'seo-fury'),
                'code' => '# Browser Caching
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 year"
    ExpiresByType image/png "access plus 1 year"
    ExpiresByType image/gif "access plus 1 year"
    ExpiresByType image/webp "access plus 1 year"
    ExpiresByType image/svg+xml "access plus 1 year"
    ExpiresByType image/x-icon "access plus 1 year"
    ExpiresByType text/css "access plus 1 month"
    ExpiresByType application/javascript "access plus 1 month"
    ExpiresByType application/x-javascript "access plus 1 month"
    ExpiresByType text/javascript "access plus 1 month"
    ExpiresByType application/font-woff "access plus 1 year"
    ExpiresByType application/font-woff2 "access plus 1 year"
    ExpiresByType font/woff2 "access plus 1 year"
    ExpiresDefault "access plus 1 month"
</IfModule>

<IfModule mod_headers.c>
    <FilesMatch "\.(ico|jpe?g|png|gif|webp|svg|woff|woff2|ttf|eot)$">
        Header set Cache-Control "public, max-age=31536000, immutable"
    </FilesMatch>
    <FilesMatch "\.(css|js)$">
        Header set Cache-Control "public, max-age=2592000"
    </FilesMatch>
</IfModule>',
            ),

            'gzip_compression' => array(
                'name' => __('Gzip Compression', 'seo-fury'),
                'desc' => __('Enable mod_deflate for text-based content', 'seo-fury'),
                'code' => '# Gzip Compression
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html
    AddOutputFilterByType DEFLATE text/css
    AddOutputFilterByType DEFLATE text/javascript
    AddOutputFilterByType DEFLATE text/xml
    AddOutputFilterByType DEFLATE text/plain
    AddOutputFilterByType DEFLATE application/javascript
    AddOutputFilterByType DEFLATE application/x-javascript
    AddOutputFilterByType DEFLATE application/json
    AddOutputFilterByType DEFLATE application/xml
    AddOutputFilterByType DEFLATE application/rss+xml
    AddOutputFilterByType DEFLATE application/atom+xml
    AddOutputFilterByType DEFLATE image/svg+xml
    AddOutputFilterByType DEFLATE application/font-woff
    AddOutputFilterByType DEFLATE font/woff2
</IfModule>',
            ),

            'security_headers' => array(
                'name' => __('Security Headers', 'seo-fury'),
                'desc' => __('Add X-Content-Type, X-Frame-Options, Referrer-Policy, XSS Protection', 'seo-fury'),
                'code' => '# Security Headers
<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
    Header set X-XSS-Protection "1; mode=block"
    Header set Referrer-Policy "strict-origin-when-cross-origin"
    Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>',
            ),

            'block_bad_bots' => array(
                'name' => __('Block Bad Bots', 'seo-fury'),
                'desc' => __('Block known malicious user agents', 'seo-fury'),
                'code' => '# Block Bad Bots
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_USER_AGENT} (AhrefsBot|SemrushBot|MJ12bot|DotBot|BLEXBot|SearchmetricsBot) [NC]
    RewriteRule .* - [F,L]
</IfModule>',
            ),

            'hotlink_protection' => array(
                'name' => __('Hotlink Protection', 'seo-fury'),
                'desc' => __('Prevent other sites from embedding your images', 'seo-fury'),
                'code' => '# Hotlink Protection
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{HTTP_REFERER} !^$
    RewriteCond %{HTTP_REFERER} !^https?://(www\.)?%{HTTP_HOST} [NC]
    RewriteCond %{HTTP_REFERER} !^https?://(www\.)?google\. [NC]
    RewriteCond %{HTTP_REFERER} !^https?://(www\.)?bing\. [NC]
    RewriteRule \.(jpe?g|png|gif|webp|svg)$ - [F,NC,L]
</IfModule>',
            ),

            'disable_directory_browsing' => array(
                'name' => __('Disable Directory Browsing', 'seo-fury'),
                'desc' => __('Prevent listing of directory contents', 'seo-fury'),
                'code' => '# Disable Directory Browsing
Options -Indexes',
            ),
        );
    }

    /**
     * Render settings page
     */
    public function render_settings_page() {
        $content     = $this->get_content();
        $is_writable = $this->is_writable();
        $is_apache   = $this->is_apache();
        $backup_time = $this->get_backup_time();
        $presets     = self::get_presets();

        ?>
        <?php if (!$is_apache) : ?>
            <div class="notice notice-warning inline" style="margin: 0 0 16px;">
                <p><strong><?php esc_html_e('Note:', 'seo-fury'); ?></strong>
                <?php esc_html_e('Your server does not appear to be running Apache. The .htaccess file may not have any effect on nginx or other servers.', 'seo-fury'); ?></p>
            </div>
        <?php endif; ?>

        <?php if (!$is_writable) : ?>
            <div class="notice notice-error inline" style="margin: 0 0 16px;">
                <p><strong><?php esc_html_e('Warning:', 'seo-fury'); ?></strong>
                <?php esc_html_e('The .htaccess file is not writable. Please check file permissions.', 'seo-fury'); ?></p>
            </div>
        <?php endif; ?>

        <div class="seo-fury-htaccess-wrap">
            <!-- Editor -->
            <div class="seo-fury-htaccess-editor-section">
                <p class="description" style="margin-bottom: 12px;">
                    <?php esc_html_e('Edit your .htaccess file directly. A backup is created automatically before each save. If changes cause a server error, they will be rolled back automatically.', 'seo-fury'); ?>
                </p>

                <textarea id="seo-fury-htaccess-editor" class="large-text code" rows="20"
                          <?php echo esc_attr( !$is_writable ? 'readonly' : '' ); ?>><?php echo esc_textarea($content); ?></textarea>

                <div class="seo-fury-htaccess-actions" style="margin-top: 12px;">
                    <button type="button" id="seo-fury-htaccess-save" class="button button-primary"
                            <?php echo esc_attr( !$is_writable ? 'disabled' : '' ); ?>>
                        <?php esc_html_e('Save .htaccess', 'seo-fury'); ?>
                    </button>

                    <?php if ($backup_time) : ?>
                        <button type="button" id="seo-fury-htaccess-restore" class="button button-secondary">
                            <?php
                            /* translators: %s: backup timestamp */
                            echo esc_html( sprintf( __('Restore Backup (%s)', 'seo-fury'), $backup_time ) ); ?>
                        </button>
                    <?php endif; ?>

                    <span id="seo-fury-htaccess-status" style="margin-left: 12px;"></span>
                </div>
            </div>

            <!-- Presets -->
            <div class="seo-fury-htaccess-presets" style="margin-top: 24px;">
                <h3><?php esc_html_e('Insert Preset Rules', 'seo-fury'); ?></h3>
                <p class="description"><?php esc_html_e('Click a button below to insert the snippet at the end of the editor. Review before saving.', 'seo-fury'); ?></p>

                <div class="seo-fury-preset-buttons" style="margin-top: 12px; display: flex; flex-wrap: wrap; gap: 8px;">
                    <?php foreach ($presets as $key => $preset) : ?>
                        <button type="button" class="button seo-fury-htaccess-preset-btn"
                                data-preset="<?php echo esc_attr($key); ?>"
                                title="<?php echo esc_attr($preset['desc']); ?>">
                            <?php echo esc_html($preset['name']); ?>
                        </button>
                    <?php endforeach; ?>
                </div>
            </div>

            <div class="seo-fury-htaccess-info" style="margin-top: 24px; padding: 12px 16px; background: var(--sf-gray-100,#f0f0f1); color: var(--sf-gray-700); border-radius: 6px;">
                <strong><?php esc_html_e('File path:', 'seo-fury'); ?></strong>
                <code><?php echo esc_html($this->htaccess_path); ?></code>
            </div>
        </div>
        <?php
    }
}
