<?php

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

class Vulnity_Static_Security {
    const OPTION_NAME = 'vulnity_hardening_settings';
    const DEFAULT_LOGIN_SLUG = 'vulnity-login';
    const LOGIN_HTACCESS_MARKER = 'Vulnity Login URL';
    const LOGIN_HTACCESS_SYNC_OPTION = 'vulnity_login_htaccess_sync_signature';
    const LOGIN_ROUTE_VERIFIED_OPTION = 'vulnity_login_route_verified_signature';
    const COMMON_PATHS_HTACCESS_MARKER = 'Vulnity Common Paths';
    const COMMON_PATHS_HTACCESS_SYNC_OPTION = 'vulnity_common_paths_htaccess_sync_signature';

private static $instance = null;
    private $login_notice_message = '';

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

        return self::$instance;
    }

    private function __construct() {
        add_filter('xmlrpc_enabled', array($this, 'filter_xmlrpc_enabled'));
        add_action('init', array($this, 'maybe_block_xmlrpc_request'));
        add_filter('rest_authentication_errors', array($this, 'filter_rest_authentication_errors'), 10, 3);
        add_filter('rest_pre_dispatch', array($this, 'filter_rest_pre_dispatch'), 10, 3);

        add_filter('authenticate', array($this, 'enforce_login_attempt_limit'), 1, 3);
        add_action('wp_login_failed', array($this, 'record_failed_login'));
        add_action('wp_login', array($this, 'reset_login_attempts'), 10, 2);
        add_filter('login_errors', array($this, 'append_login_attempt_notice'));

        add_action('init', array($this, 'register_login_rewrite_rule'));
        add_action('init', array($this, 'handle_login_access'));
        add_action('admin_init', array($this, 'maybe_sync_server_hardening_rules'), 1);
        add_filter('login_url', array($this, 'filter_login_url'), 10, 3);

        add_action('template_redirect', array($this, 'block_user_enumeration'));
        add_action('template_redirect', array($this, 'protect_common_paths'));

        add_filter('the_generator', array($this, 'filter_wp_generator'));
        add_filter('script_loader_src', array($this, 'filter_asset_version_src'), 10, 2);
        add_filter('style_loader_src', array($this, 'filter_asset_version_src'), 10, 2);
        add_action('init', array($this, 'maybe_remove_emoji_version_hints'), 1);

        add_action('login_init', array($this, 'maybe_render_login_lockout_screen'));
    }

    public function get_settings() {
        $defaults = array(
            'disable_xmlrpc' => false,
            'restrict_rest_api' => false,
            'rename_login_enabled' => false,
            'rename_login_slug' => self::DEFAULT_LOGIN_SLUG,
            'block_user_enumeration' => false,
            'protect_common_paths' => false,
            'limit_login_attempts' => false,
            'login_attempt_limit' => 5,
            'login_lockout_minutes' => 5,
            'hide_wp_version' => false,
        );

        $stored = get_option(self::OPTION_NAME, array());

        if (!is_array($stored)) {
            $stored = array();
        }

        return wp_parse_args($stored, $defaults);
    }

    public function get_setting($key, $default = null) {
        $settings = $this->get_settings();

        if (array_key_exists($key, $settings)) {
            return $settings[$key];
        }

        return $default;
    }

    public function save_settings($data) {
        $previous_settings = $this->get_settings();

        $settings = array(
            'disable_xmlrpc' => !empty($data['disable_xmlrpc']),
            'restrict_rest_api' => !empty($data['restrict_rest_api']),
            'rename_login_enabled' => !empty($data['rename_login_enabled']),
            'rename_login_slug' => $this->sanitize_login_slug($data['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG),
            'block_user_enumeration' => !empty($data['block_user_enumeration']),
            'protect_common_paths' => !empty($data['protect_common_paths']),
            'limit_login_attempts' => !empty($data['limit_login_attempts']),
            'login_attempt_limit' => isset($data['login_attempt_limit']) ? max(1, absint($data['login_attempt_limit'])) : 5,
            'login_lockout_minutes' => isset($data['login_lockout_minutes']) ? max(1, absint($data['login_lockout_minutes'])) : 5,
            'hide_wp_version' => !empty($data['hide_wp_version']),
        );

        $validation = $this->validate_login_slug_settings($settings, $previous_settings);
        if (is_wp_error($validation)) {
            return $validation;
        }

        $updated = update_option(self::OPTION_NAME, $settings);

        $login_toggle_changed = $previous_settings['rename_login_enabled'] !== $settings['rename_login_enabled'];
        $login_slug_changed = $previous_settings['rename_login_slug'] !== $settings['rename_login_slug'];

        if ($login_toggle_changed || $login_slug_changed) {
            delete_option(self::LOGIN_ROUTE_VERIFIED_OPTION);
        }

        if ($updated) {
            $login_slug_changed_when_enabled = $settings['rename_login_enabled'] && $login_slug_changed;

            if ($login_toggle_changed || $login_slug_changed_when_enabled) {
                flush_rewrite_rules();
            }
        }

        $this->maybe_sync_server_hardening_rules($settings);
        $this->maybe_sync_login_entrypoint($previous_settings, $settings);

        return $updated;
    }

    public function apply_remote_settings( array $data ) {
        $previous = $this->get_settings();

        // Merge parcial: solo sobrescribe las claves que vienen en $data
        $merged = array_merge( $previous, array_intersect_key( $data, $previous ) );

        $settings = array(
            'disable_xmlrpc'          => !empty( $merged['disable_xmlrpc'] ),
            'restrict_rest_api'       => !empty( $merged['restrict_rest_api'] ),
            'rename_login_enabled'    => !empty( $merged['rename_login_enabled'] ),
            'rename_login_slug'       => $this->sanitize_login_slug( $merged['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG ),
            'block_user_enumeration'  => !empty( $merged['block_user_enumeration'] ),
            'protect_common_paths'    => !empty( $merged['protect_common_paths'] ),
            'limit_login_attempts'    => !empty( $merged['limit_login_attempts'] ),
            'login_attempt_limit'     => max( 1, absint( $merged['login_attempt_limit'] ?? 5 ) ),
            'login_lockout_minutes'   => max( 1, absint( $merged['login_lockout_minutes'] ?? 5 ) ),
            'hide_wp_version'         => !empty( $merged['hide_wp_version'] ),
        );

        $validation = $this->validate_login_slug_settings($settings, $previous);
        if (is_wp_error($validation)) {
            return $validation;
        }

        update_option( self::OPTION_NAME, $settings );

        // Limpiar verificación de ruta si cambia el slug o el toggle de login
        $login_toggle_changed = $previous['rename_login_enabled'] !== $settings['rename_login_enabled'];
        $login_slug_changed   = $previous['rename_login_slug'] !== $settings['rename_login_slug'];
        if ( $login_toggle_changed || $login_slug_changed ) {
            delete_option( self::LOGIN_ROUTE_VERIFIED_OPTION );
        }

        // Crear/eliminar el archivo físico del entrypoint de login y regenerar rewrite rules
        $this->maybe_sync_login_entrypoint( $previous, $settings );
        if ( $login_toggle_changed || ( $settings['rename_login_enabled'] && $login_slug_changed ) ) {
            flush_rewrite_rules();
        }

        // La sync de .htaccess la ejecutará admin_init en la próxima visita al panel
        // (maybe_sync_server_hardening_rules requiere contexto is_admin())

        // Devolvemos $settings (configuración efectiva) independientemente de si update_option
        // modificó algo: un push idempotente (valores ya iguales) es éxito, no error.
        return $settings;
    }

    private function validate_login_slug_settings($settings, $previous_settings = array()) {
        if (!is_array($settings) || empty($settings['rename_login_enabled'])) {
            return true;
        }

        $normalized_slug = $this->sanitize_login_slug($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
        if ($normalized_slug === '') {
            return true;
        }

        $previous_enabled = !empty($previous_settings['rename_login_enabled']);
        $previous_slug = $this->sanitize_login_slug($previous_settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
        $slug_changed = !$previous_enabled || $previous_slug !== $normalized_slug;

        // Only revalidate when enabling the feature or changing the slug.
        if (!$slug_changed) {
            return true;
        }

        if (in_array(strtolower($normalized_slug), $this->get_reserved_login_slugs(), true)) {
            return new WP_Error(
                'vulnity_slug_reserved',
                sprintf(
                    /* translators: %s: login slug value */
                    __('The slug "%s" is reserved by WordPress and cannot be used as a login URL.', 'vulnity'),
                    esc_html($normalized_slug)
                )
            );
        }

        $conflicting_post = get_page_by_path($normalized_slug, OBJECT, 'any');
        if ($conflicting_post) {
            return new WP_Error(
                'vulnity_slug_conflict',
                sprintf(
                    /* translators: %s: login slug value */
                    __('The slug "%s" is already used by an existing page or post. Please choose a different login URL slug.', 'vulnity'),
                    esc_html($normalized_slug)
                )
            );
        }

        return true;
    }

    private function get_reserved_login_slugs() {
        return array(
            'wp-admin',
            'wp-json',
            'wp-includes',
            'wp-content',
            'wp-login',
            'wp-cron',
            'admin',
            'login',
            'dashboard',
        );
    }

    public function cleanup_on_deactivation() {
        $settings = $this->get_settings();
        $this->remove_login_entrypoint($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
        delete_option(self::LOGIN_ROUTE_VERIFIED_OPTION);
        delete_option(self::LOGIN_HTACCESS_SYNC_OPTION);
        delete_option(self::COMMON_PATHS_HTACCESS_SYNC_OPTION);
        // Best-effort marker cleanup. Keep this non-fatal and idempotent.
        $this->remove_htaccess_markers(self::LOGIN_HTACCESS_MARKER, true);
        $this->remove_htaccess_markers(self::COMMON_PATHS_HTACCESS_MARKER, true);
    }

    public function maybe_sync_server_hardening_rules($settings = null) {
        if (!is_admin() || !current_user_can('manage_options')) {
            return;
        }

        if (null === $settings) {
            $settings = $this->get_settings();
        }

        if (!is_array($settings)) {
            $settings = $this->get_settings();
        }

        $this->maybe_sync_login_htaccess_rule($settings);
        $this->maybe_sync_common_paths_htaccess_rule($settings);
    }

    private function maybe_sync_login_entrypoint($previous_settings, $settings) {
        $prev_slug = isset($previous_settings['rename_login_slug'])
            ? $previous_settings['rename_login_slug']
            : self::DEFAULT_LOGIN_SLUG;
        $next_slug = isset($settings['rename_login_slug'])
            ? $settings['rename_login_slug']
            : self::DEFAULT_LOGIN_SLUG;

        if (empty($settings['rename_login_enabled'])) {
            $this->remove_login_entrypoint($prev_slug);
            return;
        }

        if ($prev_slug !== $next_slug) {
            $this->remove_login_entrypoint($prev_slug);
        }

        $this->ensure_login_entrypoint($next_slug);
    }

    public function render_admin_page() {
        $this->maybe_handle_form_submission();

        $settings = $this->get_settings();
        $login_slug = isset($settings['rename_login_slug']) ? $settings['rename_login_slug'] : self::DEFAULT_LOGIN_SLUG;
        $login_url = $this->get_login_entrypoint_url($login_slug);
        $this->maybe_add_environment_notices($settings);
        ?>
        <div class="wrap vulnity-hardening">
            <h1><?php echo esc_html__('Vulnity Hardening', 'vulnity'); ?></h1>
            <?php settings_errors('vulnity_hardening_messages'); ?>
            <form method="post" action="<?php echo esc_url(admin_url('admin.php?page=vulnity-hardening')); ?>">
                <?php wp_nonce_field('vulnity_hardening_save', 'vulnity_hardening_nonce'); ?>
                <?php $this->render_settings_fields($settings, $login_slug, $login_url); ?>
                <?php submit_button(__('Save Changes', 'vulnity')); ?>
            </form>
        </div>
        <?php
    }

    public function render_settings_fields($settings, $login_slug, $login_url) {
        ?>
        <div class="vulnity-hardening-layout">
            <div class="vulnity-hardening-top">
                <div class="vulnity-hardening-controls">
                    <div class="vulnity-hardening-controls-grid">
                        <div class="vulnity-hardening-toggle">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="disable_xmlrpc">
                                        <input type="checkbox" id="disable_xmlrpc" name="disable_xmlrpc" value="1" <?php checked(!empty($settings['disable_xmlrpc'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="disable_xmlrpc"><b><?php echo esc_html__('Disable XML-RPC', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Blocks anonymous XML-RPC requests.', 'vulnity'); ?></span>
                                </div>
                            </div>
                        </div>

                        <div class="vulnity-hardening-toggle vulnity-hardening-toggle--stacked">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="rename_login_enabled">
                                        <input type="checkbox" id="rename_login_enabled" name="rename_login_enabled" value="1" <?php checked(!empty($settings['rename_login_enabled'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="rename_login_enabled"><b><?php echo esc_html__('Rename Login URL', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Hides wp-login.php behind a custom slug.', 'vulnity'); ?></span>
                                </div>
                            </div>
                            <div class="vulnity-hardening-inline-inputs">
                                <label class="vulnity-inline-field" for="rename_login_slug"><?php echo esc_html__('Login slug', 'vulnity'); ?></label>
                                <input type="text" id="rename_login_slug" name="rename_login_slug" value="<?php echo esc_attr($login_slug); ?>" class="regular-text" />
                            </div>
                            <p class="description vulnity-toggle-description" style="margin-top: 6px;">
                                <?php echo esc_html__('Set any slug you prefer for the new login URL.', 'vulnity'); ?>
                            </p>
                            <div class="vulnity-login-url vulnity-login-url--inline"><?php echo esc_html($login_url); ?></div>
                        </div>

                        <div class="vulnity-hardening-toggle">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="restrict_rest_api">
                                        <input type="checkbox" id="restrict_rest_api" name="restrict_rest_api" value="1" <?php checked(!empty($settings['restrict_rest_api'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="restrict_rest_api"><b><?php echo esc_html__('Restrict REST API', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Requires authentication for REST API.', 'vulnity'); ?></span>
                                </div>
                            </div>
                        </div>

                        <div class="vulnity-hardening-toggle">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="block_user_enumeration">
                                        <input type="checkbox" id="block_user_enumeration" name="block_user_enumeration" value="1" <?php checked(!empty($settings['block_user_enumeration'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="block_user_enumeration"><b><?php echo esc_html__('Block User Enumeration', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Prevents author ID scraping.', 'vulnity'); ?></span>
                                </div>
                            </div>
                        </div>

                        <div class="vulnity-hardening-toggle">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="protect_common_paths">
                                        <input type="checkbox" id="protect_common_paths" name="protect_common_paths" value="1" <?php checked(!empty($settings['protect_common_paths'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="protect_common_paths"><b><?php echo esc_html__('Protect Common Paths', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Blocks directory indexing access.', 'vulnity'); ?></span>
                                </div>
                            </div>
                        </div>

                        <div class="vulnity-hardening-toggle">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="hide_wp_version">
                                        <input type="checkbox" id="hide_wp_version" name="hide_wp_version" value="1" <?php checked(!empty($settings['hide_wp_version'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="hide_wp_version"><b><?php echo esc_html__('Hide WordPress Version', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Removes version from meta tag and asset URLs.', 'vulnity'); ?></span>
                                </div>
                            </div>
                        </div>

                        <div class="vulnity-hardening-toggle vulnity-hardening-toggle--stacked">
                            <div class="vulnity-toggle-header">
                                <div class="vulnity-toggle-row">
                                    <label class="vulnity-switch" for="limit_login_attempts">
                                        <input type="checkbox" id="limit_login_attempts" name="limit_login_attempts" value="1" <?php checked(!empty($settings['limit_login_attempts'])); ?> />
                                        <span class="vulnity-slider"></span>
                                    </label>
                                    <label class="vulnity-toggle-title" for="limit_login_attempts"><b><?php echo esc_html__('Login Attempt Limit', 'vulnity'); ?></b></label>
                                    <span class="vulnity-toggle-note"><?php echo esc_html__('Locks out repeated failures.', 'vulnity'); ?></span>
                                </div>
                            </div>
                            <div class="vulnity-hardening-number-grid">
                                <label class="vulnity-number-field" for="login_attempt_limit">
                                    <span><?php echo esc_html__('Maximum attempts', 'vulnity'); ?></span>
                                    <input type="number" id="login_attempt_limit" name="login_attempt_limit" value="<?php echo esc_attr(absint($settings['login_attempt_limit'])); ?>" min="1" />
                                </label>
                                <label class="vulnity-number-field" for="login_lockout_minutes">
                                    <span><?php echo esc_html__('Lockout minutes', 'vulnity'); ?></span>
                                    <input type="number" id="login_lockout_minutes" name="login_lockout_minutes" value="<?php echo esc_attr(absint($settings['login_lockout_minutes'])); ?>" min="1" />
                                </label>
                            </div>
                            <p class="description vulnity-toggle-description"><?php echo esc_html__('When the threshold is reached, the IP is blocked from login for the configured minutes.', 'vulnity'); ?></p>
                        </div>
                    </div>
                </div>
            </div>

        </div>
        <?php
    }

    public function maybe_handle_form_submission() {
        $request_method = vulnity_get_server_var('REQUEST_METHOD');

        if ($request_method !== 'POST') {
            return;
        }

        $page_raw = filter_input(INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        $page = $page_raw !== null ? sanitize_key(wp_unslash($page_raw)) : '';

        $tab_raw = filter_input(INPUT_GET, 'tab', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        $tab = $tab_raw !== null ? sanitize_key(wp_unslash($tab_raw)) : '';

        $allowed_targets = array(
            'vulnity-hardening',
            'vulnity',
        );

        if (!in_array($page, $allowed_targets, true)) {
            return;
        }

        if ($page === 'vulnity' && $tab !== 'hardening') {
            return;
        }

        $this->handle_form_submission();
    }

    private function handle_form_submission() {
        if (!current_user_can('manage_options')) {
            add_settings_error('vulnity_hardening_messages', 'vulnity_hardening_cap', __('You do not have permission to update these settings.', 'vulnity'));
            return;
        }

        $nonce = isset($_POST['vulnity_hardening_nonce']) ? sanitize_text_field(wp_unslash($_POST['vulnity_hardening_nonce'])) : '';

        if (!wp_verify_nonce($nonce, 'vulnity_hardening_save')) {
            add_settings_error('vulnity_hardening_messages', 'vulnity_hardening_nonce', __('Security check failed. Please try again.', 'vulnity'));
            return;
        }

        $disable_xmlrpc = filter_input(INPUT_POST, 'disable_xmlrpc', FILTER_UNSAFE_RAW);
        $restrict_rest_api = filter_input(INPUT_POST, 'restrict_rest_api', FILTER_UNSAFE_RAW);
        $rename_login_enabled = filter_input(INPUT_POST, 'rename_login_enabled', FILTER_UNSAFE_RAW);
        $rename_login_slug = filter_input(INPUT_POST, 'rename_login_slug', FILTER_UNSAFE_RAW);
        $block_user_enumeration = filter_input(INPUT_POST, 'block_user_enumeration', FILTER_UNSAFE_RAW);
        $protect_common_paths = filter_input(INPUT_POST, 'protect_common_paths', FILTER_UNSAFE_RAW);
        $limit_login_attempts = filter_input(INPUT_POST, 'limit_login_attempts', FILTER_UNSAFE_RAW);
        $login_attempt_limit = filter_input(INPUT_POST, 'login_attempt_limit', FILTER_UNSAFE_RAW);
        $login_lockout_minutes = filter_input(INPUT_POST, 'login_lockout_minutes', FILTER_UNSAFE_RAW);
        $hide_wp_version = filter_input(INPUT_POST, 'hide_wp_version', FILTER_UNSAFE_RAW);

        $rename_login_slug = is_string($rename_login_slug) ? wp_unslash($rename_login_slug) : self::DEFAULT_LOGIN_SLUG;
        $rename_login_slug_sanitized = $this->sanitize_login_slug($rename_login_slug);

        $settings = array(
            'disable_xmlrpc' => !empty($disable_xmlrpc),
            'restrict_rest_api' => !empty($restrict_rest_api),
            'rename_login_enabled' => !empty($rename_login_enabled),
            'rename_login_slug' => $rename_login_slug_sanitized,
            'block_user_enumeration' => !empty($block_user_enumeration),
            'protect_common_paths' => !empty($protect_common_paths),
            'limit_login_attempts' => !empty($limit_login_attempts),
            'login_attempt_limit' => null !== $login_attempt_limit ? max(1, absint(wp_unslash($login_attempt_limit))) : 5,
            'login_lockout_minutes' => null !== $login_lockout_minutes ? max(1, absint(wp_unslash($login_lockout_minutes))) : 5,
            'hide_wp_version' => !empty($hide_wp_version),
        );

        $result = $this->save_settings($settings);
        if (is_wp_error($result)) {
            add_settings_error(
                'vulnity_hardening_messages',
                $result->get_error_code(),
                $result->get_error_message()
            );
            return;
        }

        add_settings_error('vulnity_hardening_messages', 'vulnity_hardening_saved', __('Settings saved.', 'vulnity'), 'updated');
    }

    public function filter_xmlrpc_enabled($is_enabled) {
        if (!empty($this->get_setting('disable_xmlrpc'))) {
            return false;
        }

        return $is_enabled;
    }

    public function maybe_block_xmlrpc_request() {
        if (empty($this->get_setting('disable_xmlrpc'))) {
            return;
        }

        $requested_file = $this->get_requested_file();

        if ($requested_file === 'xmlrpc.php' && !is_user_logged_in()) {
            wp_die(esc_html__('XML-RPC is disabled.', 'vulnity'), '', array('response' => 403));
        }
    }

    public function filter_rest_authentication_errors($result, $request = null, $server = null) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
        if ($result !== null && !empty($result)) {
            return $result;
        }

        if (!$this->should_restrict_rest_route($request)) {
            return $result;
        }

        return $this->get_rest_restricted_error();
    }

    public function filter_rest_pre_dispatch($result, $server, $request) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
        if ($result !== null) {
            return $result;
        }

        if (!$this->should_restrict_rest_route($request)) {
            return $result;
        }

        return $this->get_rest_restricted_error();
    }

    private function should_restrict_rest_route($request = null) {
        if (empty($this->get_setting('restrict_rest_api'))) {
            return false;
        }

        if (is_user_logged_in()) {
            return false;
        }

        $route = $this->get_rest_route($request);
        if ($route === '') {
            return false;
        }

        return !$this->is_public_rest_route($route);
    }

    private function get_rest_restricted_error() {
        return new WP_Error('vulnity_rest_restricted', __('REST API access restricted by Vulnity.', 'vulnity'), array('status' => 401));
    }

    private function get_rest_route($request = null) {
        if (is_object($request) && method_exists($request, 'get_route')) {
            $route = (string) $request->get_route();
            if ($route !== '') {
                return '/' . ltrim($route, '/');
            }
        }

        $rest_route_raw = filter_input(INPUT_GET, 'rest_route', FILTER_UNSAFE_RAW);
        $rest_route = $rest_route_raw !== null ? trim((string) wp_unslash($rest_route_raw)) : '';
        if ($rest_route !== '') {
            return '/' . ltrim($rest_route, '/');
        }

        $request_uri = $this->get_request_uri_path();
        if ($request_uri === '') {
            return '';
        }

        $prefix = '/' . trim(rest_get_url_prefix(), '/');
        if ($prefix === '/') {
            $prefix = '/wp-json';
        }

        $position = strpos($request_uri, $prefix);
        if ($position === false) {
            return '';
        }

        $route = substr($request_uri, $position + strlen($prefix));
        if ($route === false) {
            return '';
        }

        $route = '/' . ltrim((string) $route, '/');

        return $route;
    }

    public function enforce_login_attempt_limit($user, $username, $password) {
        if (empty($this->get_setting('limit_login_attempts'))) {
            return $user;
        }

        $ip = $this->get_client_ip();

        if ($ip === '') {
            return $user;
        }

        $lockout_key = $this->get_lockout_key($ip);
        $lockout_until = get_transient($lockout_key);

        if ($lockout_until && $lockout_until > time()) {
            $remaining = max(1, ceil(($lockout_until - time()) / 60));
            return new WP_Error(
                'vulnity_login_locked',
                sprintf(
                    /* translators: %d: minutes remaining */
                    __('Too many failed attempts. Try again in %d minutes.', 'vulnity'),
                    $remaining
                )
            );
        }

        return $user;
    }

    public function record_failed_login($username) {
        if (empty($this->get_setting('limit_login_attempts'))) {
            return;
        }

        $ip = $this->get_client_ip();

        if ($ip === '') {
            return;
        }

        $attempt_key = $this->get_attempt_key($ip);
        $lockout_key = $this->get_lockout_key($ip);
        $limit = (int) $this->get_setting('login_attempt_limit', 5);
        $lockout_minutes = (int) $this->get_setting('login_lockout_minutes', 5);
        $attempts = (int) get_transient($attempt_key);

        $attempts++;

        if ($attempts >= $limit) {
            $lockout_until = time() + ($lockout_minutes * MINUTE_IN_SECONDS);
            set_transient($lockout_key, $lockout_until, $lockout_minutes * MINUTE_IN_SECONDS);
            delete_transient($attempt_key);

            $this->set_login_notice(
                $ip,
                sprintf(
                    /* translators: %d: minutes */
                    __('Too many failed attempts. Try again in %d minutes.', 'vulnity'),
                    $lockout_minutes
                ),
                $lockout_minutes
            );
        } else {
            set_transient($attempt_key, $attempts, $lockout_minutes * MINUTE_IN_SECONDS);

            $remaining = max(1, $limit - $attempts);

            $this->set_login_notice(
                $ip,
                sprintf(
                    /* translators: %d: remaining attempts */
                    _n(
                        'Warning: %d attempt remaining before lockout.',
                        'Warning: %d attempts remaining before lockout.',
                        $remaining,
                        'vulnity'
                    ),
                    $remaining
                ),
                $lockout_minutes
            );
        }
    }

    public function reset_login_attempts($user_login, $user) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
        if (empty($this->get_setting('limit_login_attempts'))) {
            return;
        }

        $ip = $this->get_client_ip();

        if ($ip === '') {
            return;
        }

        delete_transient($this->get_attempt_key($ip));
        delete_transient($this->get_lockout_key($ip));
        delete_transient($this->get_notice_key($ip));
    }

    public function append_login_attempt_notice($errors) {
        if (empty($this->get_setting('limit_login_attempts'))) {
            return $errors;
        }

        $ip = $this->get_client_ip();

        if ($ip === '') {
            return $errors;
        }

        $notice = $this->consume_login_notice($ip);

        if ($notice === '') {
            return $errors;
        }

        $notice_html = esc_html($notice);

        if ($errors === '') {
            return $notice_html;
        }

        return $errors . '<br />' . $notice_html;
    }

    public function register_login_rewrite_rule() {
        if (empty($this->get_setting('rename_login_enabled'))) {
            return;
        }

        $slug = $this->get_setting('rename_login_slug', self::DEFAULT_LOGIN_SLUG);
        add_rewrite_rule('^' . preg_quote($slug, '#') . '/?$', 'wp-login.php', 'top');
    }

    public function handle_login_access() {
        if (empty($this->get_setting('rename_login_enabled'))) {
            return;
        }

        $login_slug = $this->get_setting('rename_login_slug', self::DEFAULT_LOGIN_SLUG);
        $requested_path = trim($this->get_request_uri_path(), '/');
        $normalized_slug = trim($login_slug, '/');
        $requested_file = $this->get_requested_file();
        $bypass_login = $this->is_internal_login_request();
        $can_enforce_hidden_login = $this->can_enforce_hidden_login($normalized_slug);

        if ($bypass_login && ($requested_path === '' || $requested_path === '/')) {
            $this->load_login_screen();
        }

        if ($this->is_wp_admin_request($requested_path, $requested_file) && !is_user_logged_in()) {
            status_header(404);
            nocache_headers();
            exit;
        }

        if ($requested_path === $normalized_slug) {
            $this->mark_login_route_verified($normalized_slug);
            $this->load_login_screen();
        }

        $request_method = isset($_SERVER['REQUEST_METHOD']) ? strtoupper(vulnity_get_server_var('REQUEST_METHOD')) : 'GET';

        if ($requested_file === 'wp-login.php' && !is_user_logged_in()) {
            if ($bypass_login) {
                return;
            }

            if ($request_method !== 'GET' && $request_method !== 'HEAD') {
                return;
            }

            $allowed_query_keys = array(
                'redirect_to',
                'reauth',
                'action',
                'loggedout',
                'checkemail',
                'error',
                'login',
                'key',
                'wp_lang',
            );
            $query_args = array();

            foreach ($allowed_query_keys as $key) {
                $filtered_value = filter_input(INPUT_GET, $key, FILTER_UNSAFE_RAW);

                if (null === $filtered_value) {
                    continue;
                }

                if (is_array($filtered_value)) {
                    $unslashed_value = function_exists('wp_unslash') ? wp_unslash($filtered_value) : $filtered_value;
                    $sanitized_value = array_map('sanitize_text_field', (array) $unslashed_value);
                } else {
                    $sanitized_value = sanitize_text_field(function_exists('wp_unslash') ? wp_unslash($filtered_value) : $filtered_value);
                }

                if ($sanitized_value !== '' && $sanitized_value !== array()) {
                    $query_args[$key] = $sanitized_value;
                }
            }

            // Direct access to wp-login.php must not reveal the login endpoint.
            // Only known login actions with query args are forwarded to the custom slug.
            if (empty($query_args)) {
                status_header(404);
                nocache_headers();
                exit;
            }

            $redirect_url = $this->get_login_entrypoint_url($login_slug);
            $redirect_url = add_query_arg($query_args, $redirect_url);

            wp_safe_redirect($redirect_url, 302);
            exit;
        }
    }

    public function filter_login_url($login_url, $redirect, $force_reauth) {
        if (empty($this->get_setting('rename_login_enabled'))) {
            return $login_url;
        }

        if ($this->is_internal_login_request()) {
            return $login_url;
        }

        $slug = $this->get_setting('rename_login_slug', self::DEFAULT_LOGIN_SLUG);
        $url = $this->get_login_entrypoint_url($slug);

        if (!empty($redirect)) {
            $url = add_query_arg('redirect_to', urlencode($redirect), $url);
        }

        if ($force_reauth) {
            $url = add_query_arg('reauth', '1', $url);
        }

        return esc_url($url);
    }

    private function maybe_add_environment_notices($settings) {
        if (!is_admin() || !current_user_can('manage_options')) {
            return;
        }

        $entrypoint_notice = get_transient('vulnity_login_entrypoint_notice');
        if (is_string($entrypoint_notice) && $entrypoint_notice !== '') {
            add_settings_error(
                'vulnity_hardening_messages',
                'vulnity_login_entrypoint_notice',
                $entrypoint_notice,
                'warning'
            );
            delete_transient('vulnity_login_entrypoint_notice');
        }

        if (!empty($settings['rename_login_enabled'])) {
            $slug = $this->sanitize_login_slug($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
            if (!$this->can_enforce_hidden_login($slug)) {
                $fallback_url = esc_url(add_query_arg('vulnity-login', '1', home_url('/')));
                add_settings_error(
                    'vulnity_hardening_messages',
                    'vulnity_login_compatibility_mode',
                    sprintf(
                        /* translators: %s: URL to the fallback login screen */
                        __('Rename Login URL is running in compatibility mode. The custom slug may not be reachable on this hosting. Use %s to access the login screen.', 'vulnity'),
                        $fallback_url
                    ),
                    'warning'
                );
            }
        }

        if (!empty($settings['protect_common_paths']) && !$this->has_common_paths_htaccess_rewrite_rule()) {
            add_settings_error(
                'vulnity_hardening_messages',
                'vulnity_common_paths_partial',
                __('Protect Common Paths may be partial on this hosting because server-level rewrite rules are unavailable. Basic protection still applies where WordPress handles the request.', 'vulnity'),
                'warning'
            );
        }
    }

    public function filter_wp_generator( $gen ) {
        if ( !empty( $this->get_setting( 'hide_wp_version' ) ) ) {
            return '';
        }

        return $gen;
    }

    public function filter_asset_version_src( $src, $handle ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
        if ( empty( $this->get_setting( 'hide_wp_version' ) ) ) {
            return $src;
        }

        if ( !is_string( $src ) || strpos( $src, 'ver=' ) === false ) {
            return $src;
        }

        $base = strtok( $src, '?' );
        parse_str( (string) wp_parse_url( $src, PHP_URL_QUERY ), $query_args );
        unset( $query_args['ver'] );

        return empty( $query_args ) ? $base : $base . '?' . http_build_query( $query_args );
    }

    public function maybe_remove_emoji_version_hints() {
        if ( empty( $this->get_setting( 'hide_wp_version' ) ) ) {
            return;
        }
        // The wp-emoji-settings inline JSON contains s.w.org/images/core/emoji/X.Y.Z/
        // which Wappalyzer fingerprints to identify the exact WordPress version.
        // Removing the emoji detection script is the only way to suppress this leak.
        remove_action( 'wp_head',               'print_emoji_detection_script', 7 );
        remove_action( 'wp_print_styles',        'print_emoji_styles' );
        remove_action( 'admin_print_scripts',    'print_emoji_detection_script' );
        remove_action( 'admin_print_styles',     'print_emoji_styles' );
        remove_filter( 'the_content_feed',       'wp_staticize_emoji' );
        remove_filter( 'comment_text_rss',       'wp_staticize_emoji' );
        remove_filter( 'wp_mail',                'wp_staticize_emoji_for_email' );
    }

    public function block_user_enumeration() {
        if (empty($this->get_setting('block_user_enumeration'))) {
            return;
        }

        if (is_user_logged_in()) {
            return;
        }

        $author_query = get_query_var('author');

        if (is_author() || (is_numeric($author_query) && intval($author_query) > 0)) {
            status_header(404);
            nocache_headers();
            exit;
        }
    }

    public function protect_common_paths() {
        $request_uri = $this->get_request_uri_path();

        if ($request_uri === '') {
            return;
        }

        $normalized = trailingslashit($request_uri);

        // Always protect sensitive Vulnity directories (PHP-only fallback for all servers)
        $always_protected = array();

        // Protect secure Vulnity runtime storage directory.
        $always_protected[] = '/wp-content/vulnity-data/';

        // Protect legacy public runtime directories while they are being phased out.
        $always_protected[] = '/wp-content/vulnity-firewall/';
        $always_protected[] = '/wp-content/vulnity-logs/';

        $upload_dir = wp_upload_dir(null, false);
        if (is_array($upload_dir) && !empty($upload_dir['basedir'])) {
            $logs_path = $upload_dir['basedir'] . '/vulnity-logs/';
            $logs_relative = str_replace(ABSPATH, '', $logs_path);
            $always_protected[] = '/' . trim($logs_relative, '/') . '/';

            $legacy_firewall_path = $upload_dir['basedir'] . '/vulnity-firewall/';
            $legacy_firewall_relative = str_replace(ABSPATH, '', $legacy_firewall_path);
            $always_protected[] = '/' . trim($legacy_firewall_relative, '/') . '/';
        }

        foreach ($always_protected as $path) {
            if (strpos($normalized, $path) === 0) {
                status_header(403);
                nocache_headers();
                die('Access forbidden');
            }
        }

        // Protect common paths only if feature is enabled
        if (empty($this->get_setting('protect_common_paths'))) {
            return;
        }

        $protected_paths = array(
            '/wp-content/uploads/',
            '/wp-content/plugins/',
            '/wp-content/themes/',
        );

        if (in_array($normalized, $protected_paths, true) && !$this->request_targets_file($request_uri)) {
            status_header(403);
            nocache_headers();
            wp_die(esc_html__('Directory access is restricted.', 'vulnity'), '', array('response' => 403));
        }
    }

    private function get_client_ip() {
        $ip = vulnity_get_server_var('REMOTE_ADDR');

        if (!is_string($ip)) {
            return '';
        }

        return trim($ip);
    }

    private function set_login_notice($ip, $message, $expiration_minutes) {
        $message = wp_strip_all_tags((string) $message);
        $this->login_notice_message = $message;

        if ($ip === '') {
            return;
        }

        set_transient(
            $this->get_notice_key($ip),
            $message,
            max(1, (int) $expiration_minutes) * MINUTE_IN_SECONDS
        );
    }

    private function consume_login_notice($ip) {
        if ($this->login_notice_message !== '') {
            $message = $this->login_notice_message;
            $this->login_notice_message = '';
            return $message;
        }

        $notice_key = $this->get_notice_key($ip);
        $stored_notice = get_transient($notice_key);

        if ($stored_notice !== false) {
            delete_transient($notice_key);
            return (string) $stored_notice;
        }

        return '';
    }

    private function get_attempt_key($ip) {
        return 'vulnity_login_attempts_' . md5($ip);
    }

    private function get_lockout_key($ip) {
        return 'vulnity_login_lockout_' . md5($ip);
    }

    private function get_notice_key($ip) {
        return 'vulnity_login_notice_' . md5($ip);
    }

    private function is_public_rest_route($route) {
        if (!is_string($route) || $route === '') {
            return false;
        }

        $route = '/' . ltrim($route, '/');

        $public_routes = array(
            '/oembed/1.0/embed',
            '/oembed/1.0/proxy',
            '/vulnity/v1/',
        );

        foreach ($public_routes as $public_route) {
            if (strpos($route, $public_route) === 0) {
                return true;
            }
        }

        return false;
    }

    private function get_requested_file() {
        $script_name = vulnity_get_server_var('SCRIPT_NAME');

        return basename($script_name);
    }

    private function can_enforce_hidden_login($slug) {
        $normalized_slug = trim($this->sanitize_login_slug($slug), '/');

        if ($normalized_slug === '') {
            return false;
        }

        if ($this->has_login_htaccess_rewrite_rule($normalized_slug)) {
            return true;
        }

        if (!$this->has_pretty_permalinks()) {
            return false;
        }

        return $this->is_login_route_verified($normalized_slug);
    }

    private function mark_login_route_verified($slug) {
        $signature = $this->get_login_route_signature($slug);

        if ($signature === '') {
            return;
        }

        update_option(self::LOGIN_ROUTE_VERIFIED_OPTION, $signature, false);
    }

    private function is_login_route_verified($slug) {
        $signature = $this->get_login_route_signature($slug);

        if ($signature === '') {
            return false;
        }

        return get_option(self::LOGIN_ROUTE_VERIFIED_OPTION, '') === $signature;
    }

    private function get_login_route_signature($slug) {
        $normalized_slug = trim($this->sanitize_login_slug($slug), '/');

        if ($normalized_slug === '') {
            return '';
        }

        $site_key = untrailingslashit((string) home_url('/'));
        return md5(strtolower($site_key . '|' . $normalized_slug));
    }

    private function is_wp_admin_request($requested_path, $requested_file) {
        if ($requested_path === '') {
            return false;
        }

        if (strpos($requested_path, 'wp-admin') !== 0) {
            return false;
        }

        $public_admin_files = array(
            'admin-ajax.php',
            'admin-post.php',
        );

        return !in_array($requested_file, $public_admin_files, true);
    }

    private function get_request_uri_path() {
        $request_uri = vulnity_get_server_var('REQUEST_URI');

        if ($request_uri === '') {
            return '';
        }

        $path = wp_parse_url($request_uri, PHP_URL_PATH);

        if (!is_string($path)) {
            return '';
        }

        return $path;
    }

    private function has_pretty_permalinks() {
        $structure = function_exists('get_option') ? (string) get_option('permalink_structure') : '';

        return $structure !== '';
    }

    private function get_login_entrypoint_url($slug) {
        $normalized_slug = trim($this->sanitize_login_slug($slug), '/');

        if ($normalized_slug !== '' && $this->has_login_entrypoint_file($normalized_slug)) {
            return $this->build_login_url($normalized_slug);
        }

        if ($normalized_slug !== '' && $this->has_login_htaccess_rewrite_rule($normalized_slug)) {
            return $this->build_login_url($normalized_slug);
        }

        if (!$this->has_pretty_permalinks()) {
            return add_query_arg('vulnity-login', '1', home_url('/'));
        }

        return $this->build_login_url($normalized_slug);
    }

    private function get_login_entrypoint_paths($slug) {
        $normalized_slug = trim($this->sanitize_login_slug($slug), '/');

        if ($normalized_slug === '') {
            $normalized_slug = self::DEFAULT_LOGIN_SLUG;
        }

        $segments = array_filter(explode('/', $normalized_slug), 'strlen');
        if (empty($segments)) {
            $segments = array(self::DEFAULT_LOGIN_SLUG);
        }

        $home_path = function_exists('get_home_path') ? get_home_path() : ABSPATH;
        $home_path = trailingslashit($home_path);
        $dir = $home_path . implode('/', $segments);

        return array(
            'dir' => $dir,
            'file' => trailingslashit($dir) . 'index.php',
            'slug' => $normalized_slug,
        );
    }

    private function has_login_entrypoint_file($slug) {
        $paths = $this->get_login_entrypoint_paths($slug);
        $file = $paths['file'];

        if (!file_exists($file) || !is_readable($file)) {
            return false;
        }

        $contents = file_get_contents($file);
        if (!is_string($contents)) {
            return false;
        }

        return strpos($contents, 'Vulnity Login Entry') !== false;
    }

    private function ensure_login_entrypoint($slug) {
        $paths = $this->get_login_entrypoint_paths($slug);
        $dir = $paths['dir'];
        $file = $paths['file'];

        if ($this->has_login_entrypoint_file($slug)) {
            return true;
        }

        if (file_exists($file)) {
            $this->set_login_entrypoint_notice(
                sprintf(
                    /* translators: %s: File path to the login entrypoint */
                    __('The login entrypoint already exists at %s and was not created by Vulnity.', 'vulnity'),
                    $file
                )
            );
            return false;
        }

        $filesystem = function_exists('vulnity_get_wp_filesystem') ? vulnity_get_wp_filesystem() : null;
        if (!$filesystem || !method_exists($filesystem, 'mkdir') || !method_exists($filesystem, 'put_contents')) {
            $this->set_login_entrypoint_notice(
                __('Vulnity could not create the custom login entrypoint. Please ensure the WordPress root is writable or enable permalinks.', 'vulnity')
            );
            return false;
        }

        if (!is_dir($dir)) {
            $chmod = defined('FS_CHMOD_DIR') ? FS_CHMOD_DIR : 0755;
            if (!$filesystem->mkdir($dir, $chmod)) {
                $this->set_login_entrypoint_notice(
                    __('Vulnity could not create the custom login entrypoint directory. Please ensure the WordPress root is writable.', 'vulnity')
                );
                return false;
            }
        }

        $home_path = function_exists('get_home_path') ? get_home_path() : ABSPATH;
        $home_path = trailingslashit($home_path);
        $wp_login_path = $home_path . 'wp-login.php';
        $entry_content = "<?php\n";
        $entry_content .= "// Vulnity Login Entry (auto-generated).\n";
        $entry_content .= "if (!isset(\$_GET['vulnity-login'])) { \$_GET['vulnity-login'] = '1'; }\n";
        $entry_content .= "if (!defined('ABSPATH')) { define('ABSPATH', '" . addslashes($home_path) . "'); }\n";
        $entry_content .= "require_once '" . addslashes($wp_login_path) . "';\n";

        $chmod = defined('FS_CHMOD_FILE') ? FS_CHMOD_FILE : false;
        $write_ok = $filesystem->put_contents($file, $entry_content, $chmod);

        if (!$write_ok) {
            $this->set_login_entrypoint_notice(
                __('Vulnity could not write the custom login entrypoint file. Please ensure the WordPress root is writable.', 'vulnity')
            );
            return false;
        }

        return true;
    }

    private function remove_login_entrypoint($slug) {
        $paths = $this->get_login_entrypoint_paths($slug);
        $dir = $paths['dir'];
        $file = $paths['file'];

        if (!file_exists($file)) {
            return;
        }

        $contents = file_get_contents($file);
        if (!is_string($contents) || strpos($contents, 'Vulnity Login Entry') === false) {
            return;
        }

        $filesystem = function_exists('vulnity_get_wp_filesystem') ? vulnity_get_wp_filesystem() : null;
        if ($filesystem && method_exists($filesystem, 'delete')) {
            $filesystem->delete($file);
        }

        if (is_dir($dir)) {
            $items = @scandir($dir);
            if (is_array($items)) {
                $items = array_diff($items, array('.', '..'));
                if (empty($items)) {
                    // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem already used above
                    @rmdir($dir);
                }
            }
        }
    }

    private function set_login_entrypoint_notice($message) {
        if (!is_string($message) || $message === '') {
            return;
        }

        set_transient('vulnity_login_entrypoint_notice', $message, HOUR_IN_SECONDS);
    }

    public function maybe_render_login_lockout_screen() {
        if (empty($this->get_setting('limit_login_attempts'))) {
            return;
        }

        $ip = $this->get_client_ip();

        if ($ip === '') {
            return;
        }

        $lockout_key = $this->get_lockout_key($ip);
        $lockout_until = get_transient($lockout_key);

        if (!$lockout_until) {
            return;
        }

        if ($lockout_until <= time()) {
            delete_transient($lockout_key);
            return;
        }

        $remaining_minutes = max(1, ceil(($lockout_until - time()) / MINUTE_IN_SECONDS));
        $unlock_time = wp_date('F j, Y g:i A', $lockout_until);

        $this->render_login_lockout_page($ip, $remaining_minutes, $unlock_time);
    }

    private function render_login_lockout_page($ip, $remaining_minutes, $unlock_time) {
        $logo_url = plugins_url('../assets/Logo_Vulnity.png', __FILE__);
        $style_url = plugins_url('../assets/block-page.css', __FILE__);
        $style_markup = $this->get_block_page_styles_markup($style_url);

        $ip_safe = esc_html($ip);
        $remaining_safe = esc_html($remaining_minutes);
        $unlock_safe = esc_html($unlock_time);
        $logo_safe = esc_url($logo_url);

        $html_template = implode('', array(
            '<!DOCTYPE html>',
            '<html>',
            '<head>',
            '<meta charset="UTF-8">',
            '<meta name="viewport" content="width=device-width,initial-scale=1">',
            '<title>' . esc_html__('Login temporarily locked', 'vulnity') . '</title>',
            '%1$s',
            '</head>',
            '<body>',
            '<div class="c">',
            '    <div class="logo"><img src="%2$s" alt="Vulnity Security Logo"></div>',
            '    <h1>' . esc_html__('Login temporarily locked', 'vulnity') . '</h1>',
            '    <div class="st">' . esc_html__('Too many failed login attempts were detected from your network.', 'vulnity') . '</div>',
            '    <div class="b">',
            '        <div class="d"><div class="l">' . esc_html__('Your IP Address', 'vulnity') . '</div><div class="v">%3$s</div></div>',
            '        <div class="d"><div class="l">' . esc_html__('Lockout reason', 'vulnity') . '</div><div class="v">' . esc_html__('Too many failed login attempts', 'vulnity') . '</div></div>',
            '        <div class="d"><div class="l">' . esc_html__('Try again in', 'vulnity') . '</div><div class="v">%4$s ' . esc_html__('minutes', 'vulnity') . '</div></div>',
            '        <div class="d"><div class="l">' . esc_html__('Unlocks at', 'vulnity') . '</div><div class="v">%5$s</div></div>',
            '    </div>',
            '    <div class="f"><strong>' . esc_html__('Protected by Vulnity Security', 'vulnity') . '</strong><br>' . esc_html__('Login access is temporarily blocked for this IP.', 'vulnity') . '</div>',
            '</div>',
            '</body>',
            '</html>'
        ));

        $html = sprintf(
            $html_template,
            $style_markup,
            $logo_safe,
            $ip_safe,
            $remaining_safe,
            $unlock_safe
        );

        echo wp_kses($html, $this->get_block_page_allowed_tags());
        exit;
    }

    private function get_block_page_allowed_tags() {
        return array(
            'html' => array(),
            'head' => array(),
            'meta' => array(
                'charset' => true,
                'name'    => true,
                'content' => true,
            ),
            'title' => array(),
            'link'  => array(
                'rel'  => true,
                'href' => true,
            ),
            'body'  => array(
                'class' => true,
            ),
            'div'   => array(
                'class' => true,
            ),
            'img'   => array(
                'src'   => true,
                'alt'   => true,
            ),
            'h1'    => array(),
            'strong'=> array(),
            'br'    => array(),
        );
    }

    private function get_block_page_styles_markup($style_url) {
        $handle        = 'vulnity-block-page';
        $markup        = '';
        $safe_url      = esc_url($style_url);

        if (function_exists('wp_register_style') && function_exists('wp_enqueue_style')) {
            $version = defined('VULNITY_VERSION') ? VULNITY_VERSION : null;

            if (!function_exists('wp_style_is') || !wp_style_is($handle, 'registered')) {
                wp_register_style($handle, $safe_url, array(), $version);
            }

            wp_enqueue_style($handle);

            if (function_exists('wp_print_styles')) {
                ob_start();
                wp_print_styles($handle);
                $markup = (string) ob_get_clean();
            }
        }

        return $markup;
    }

    private function request_targets_file($request_uri) {
        $basename = basename($request_uri);

        return strpos($basename, '.') !== false;
    }

    private function sanitize_login_slug($slug) {
        $slug = trim((string) $slug);
        $slug = trim($slug, " \t\n\r\0\x0B/");

        if ($slug === '') {
            return self::DEFAULT_LOGIN_SLUG;
        }

        $segments = array_filter(explode('/', $slug), 'strlen');
        $clean_segments = array();

        foreach ($segments as $segment) {
            $segment = sanitize_text_field($segment);
            $segment = preg_replace('/[^A-Za-z0-9\-_\.~ ]/', '', $segment);
            $segment = preg_replace('/\s+/', '-', $segment);

            if ($segment !== '') {
                $clean_segments[] = $segment;
            }
        }

        if (empty($clean_segments)) {
            return self::DEFAULT_LOGIN_SLUG;
        }

        return implode('/', $clean_segments);
    }

    public function maybe_sync_login_htaccess_rule($settings = null) {
        if (!$this->should_manage_htaccess_rules()) {
            delete_option(self::LOGIN_HTACCESS_SYNC_OPTION);
            return;
        }

        if (!is_array($settings)) {
            $settings = $this->get_settings();
        }

        $signature = $this->get_login_htaccess_signature($settings);
        $stored_signature = get_option(self::LOGIN_HTACCESS_SYNC_OPTION, '');
        $marker_exists = $this->has_login_htaccess_marker();
        $enabled = !empty($settings['rename_login_enabled']);

        if ($stored_signature === $signature && (($enabled && $marker_exists) || (!$enabled && !$marker_exists))) {
            return;
        }

        if ($this->sync_login_htaccess_rule($settings)) {
            update_option(self::LOGIN_HTACCESS_SYNC_OPTION, $signature, false);
        } else {
            delete_option(self::LOGIN_HTACCESS_SYNC_OPTION);
        }
    }

    public function maybe_sync_common_paths_htaccess_rule($settings = null) {
        if (!$this->should_manage_htaccess_rules()) {
            delete_option(self::COMMON_PATHS_HTACCESS_SYNC_OPTION);
            return;
        }

        if (!is_array($settings)) {
            $settings = $this->get_settings();
        }

        $signature = $this->get_common_paths_htaccess_signature($settings);
        $stored_signature = get_option(self::COMMON_PATHS_HTACCESS_SYNC_OPTION, '');
        $marker_exists = $this->has_common_paths_htaccess_marker();
        $enabled = !empty($settings['protect_common_paths']);

        if ($stored_signature === $signature && (($enabled && $marker_exists) || (!$enabled && !$marker_exists))) {
            return;
        }

        if ($this->sync_common_paths_htaccess_rule($settings)) {
            update_option(self::COMMON_PATHS_HTACCESS_SYNC_OPTION, $signature, false);
        } else {
            delete_option(self::COMMON_PATHS_HTACCESS_SYNC_OPTION);
        }
    }

    private function get_login_htaccess_signature($settings) {
        $enabled = !empty($settings['rename_login_enabled']);
        $slug = $this->sanitize_login_slug($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);

        if (!$enabled) {
            return 'disabled';
        }

        return 'enabled:' . strtolower($slug);
    }

    private function sync_login_htaccess_rule($settings) {
        $lines = array();
        $enabled = !empty($settings['rename_login_enabled']);

        if ($enabled) {
            $slug = $this->sanitize_login_slug($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
            $normalized_slug = trim($slug, '/');

            if ($normalized_slug !== '') {
                $escaped_slug = preg_quote($normalized_slug, '/');
                $lines[] = '<IfModule mod_rewrite.c>';
                $lines[] = 'RewriteEngine On';
                $lines[] = 'RewriteRule ^' . $escaped_slug . '/?$ wp-login.php?vulnity-login=1 [QSA,L]';
                $lines[] = '</IfModule>';
            }
        }

        return $this->insert_login_htaccess_markers($lines);
    }

    private function get_common_paths_htaccess_signature($settings) {
        return !empty($settings['protect_common_paths']) ? 'enabled' : 'disabled';
    }

    private function sync_common_paths_htaccess_rule($settings) {
        $lines = array();

        if (!empty($settings['protect_common_paths'])) {
            $lines[] = '<IfModule mod_rewrite.c>';
            $lines[] = 'RewriteEngine On';
            $lines[] = 'RewriteRule ^wp-content/(plugins|themes|uploads)/?$ - [F,L]';
            $lines[] = '</IfModule>';
        }

        return $this->insert_common_paths_htaccess_markers($lines);
    }

    private function has_login_htaccess_rewrite_rule($slug) {
        $htaccess_path = $this->get_htaccess_path();

        if (!file_exists($htaccess_path) || !is_readable($htaccess_path)) {
            return false;
        }

        $contents = file_get_contents($htaccess_path);
        if ($contents === false) {
            return false;
        }

        $escaped_slug = preg_quote(trim($slug, '/'), '/');
        $pattern = '/RewriteRule\\s+\\^' . $escaped_slug . '\\/\\?\\$\\s+wp-login\\.php\\?vulnity-login=1\\s+\\[QSA,L\\]/i';

        return preg_match($pattern, $contents) === 1;
    }

    private function has_common_paths_htaccess_rewrite_rule() {
        $htaccess_path = $this->get_htaccess_path();

        if (!file_exists($htaccess_path) || !is_readable($htaccess_path)) {
            return false;
        }

        $contents = file_get_contents($htaccess_path);
        if ($contents === false) {
            return false;
        }

        $pattern = '/RewriteRule\\s+\\^wp-content\\/(plugins\\|themes\\|uploads)\\/\\?\\$\\s+-\\s+\\[F,L\\]/i';

        return preg_match($pattern, $contents) === 1;
    }

    private function insert_login_htaccess_markers($lines) {
        if (!function_exists('insert_with_markers')) {
            $misc_include = ABSPATH . 'wp-admin/includes/misc.php';
            if (file_exists($misc_include)) {
                require_once $misc_include;
            }
        }

        if (!function_exists('get_home_path')) {
            $file_include = ABSPATH . 'wp-admin/includes/file.php';
            if (file_exists($file_include)) {
                require_once $file_include;
            }
        }

        if (!function_exists('insert_with_markers')) {
            return false;
        }

        $htaccess_path = $this->get_htaccess_path();
        $htaccess_dir = dirname($htaccess_path);

        if (file_exists($htaccess_path)) {
            if (!$this->path_is_writable($htaccess_path)) {
                return false;
            }
        } elseif (!is_dir($htaccess_dir) || !$this->path_is_writable($htaccess_dir)) {
            return false;
        }

        $result = insert_with_markers($htaccess_path, self::LOGIN_HTACCESS_MARKER, $lines);

        return $result !== false;
    }

    private function insert_common_paths_htaccess_markers($lines) {
        if (!function_exists('insert_with_markers')) {
            $misc_include = ABSPATH . 'wp-admin/includes/misc.php';
            if (file_exists($misc_include)) {
                require_once $misc_include;
            }
        }

        if (!function_exists('get_home_path')) {
            $file_include = ABSPATH . 'wp-admin/includes/file.php';
            if (file_exists($file_include)) {
                require_once $file_include;
            }
        }

        if (!function_exists('insert_with_markers')) {
            return false;
        }

        $htaccess_path = $this->get_htaccess_path();
        $htaccess_dir = dirname($htaccess_path);

        if (file_exists($htaccess_path)) {
            if (!$this->path_is_writable($htaccess_path)) {
                return false;
            }
        } elseif (!is_dir($htaccess_dir) || !$this->path_is_writable($htaccess_dir)) {
            return false;
        }

        $result = insert_with_markers($htaccess_path, self::COMMON_PATHS_HTACCESS_MARKER, $lines);

        return $result !== false;
    }

    private function has_login_htaccess_marker() {
        $htaccess_path = $this->get_htaccess_path();

        if (!file_exists($htaccess_path) || !is_readable($htaccess_path)) {
            return false;
        }

        $contents = file_get_contents($htaccess_path);
        if ($contents === false) {
            return false;
        }

        return strpos($contents, '# BEGIN ' . self::LOGIN_HTACCESS_MARKER) !== false;
    }

    private function has_common_paths_htaccess_marker() {
        $htaccess_path = $this->get_htaccess_path();

        if (!file_exists($htaccess_path) || !is_readable($htaccess_path)) {
            return false;
        }

        $contents = file_get_contents($htaccess_path);
        if ($contents === false) {
            return false;
        }

        return strpos($contents, '# BEGIN ' . self::COMMON_PATHS_HTACCESS_MARKER) !== false;
    }

    private function should_manage_htaccess_rules() {
        $override = apply_filters('vulnity_manage_htaccess_rules', null);
        if (is_bool($override)) {
            return $override;
        }

        $server_software_raw = filter_input(INPUT_SERVER, 'SERVER_SOFTWARE', FILTER_SANITIZE_FULL_SPECIAL_CHARS);
        $server_software = is_string($server_software_raw) ? strtolower(sanitize_text_field($server_software_raw)) : '';

        $looks_like_apache = (strpos($server_software, 'apache') !== false || strpos($server_software, 'litespeed') !== false);
        $looks_like_nginx = strpos($server_software, 'nginx') !== false;

        if ($looks_like_apache) {
            return true;
        }

        if ($looks_like_nginx) {
            return false;
        }

        // Conservative fallback for unknown environments: never create new .htaccess files.
        return file_exists($this->get_htaccess_path());
    }

    private function remove_htaccess_markers($marker, $force = false) {
        if (!$force && !$this->should_manage_htaccess_rules()) {
            return false;
        }

        if (!is_string($marker) || $marker === '') {
            return false;
        }

        return $this->remove_htaccess_marker_block($marker);
    }

    private function remove_htaccess_marker_block($marker) {
        $htaccess_path = $this->get_htaccess_path();

        if (!file_exists($htaccess_path)) {
            return true;
        }

        if (!is_readable($htaccess_path) || !$this->path_is_writable($htaccess_path)) {
            return false;
        }

        $contents = file_get_contents($htaccess_path);
        if ($contents === false) {
            return false;
        }

        list($updated, $changed) = $this->strip_marker_blocks_from_contents($contents, $marker);
        if (!$changed) {
            return true;
        }

        return file_put_contents($htaccess_path, $updated, LOCK_EX) !== false;
    }

    private function strip_marker_blocks_from_contents($contents, $marker) {
        if (!is_string($contents) || !is_string($marker) || $marker === '') {
            return array($contents, false);
        }

        $begin_marker = '# BEGIN ' . $marker;
        $end_marker = '# END ' . $marker;
        $changed = false;
        $offset = 0;

        while (($begin_pos = strpos($contents, $begin_marker, $offset)) !== false) {
            $prefix = substr($contents, 0, $begin_pos);
            $line_start = strrpos($prefix, "\n");
            $line_start = ($line_start === false) ? 0 : ($line_start + 1);

            $end_pos = strpos($contents, $end_marker, $begin_pos);
            if ($end_pos === false) {
                break;
            }

            $line_end = strpos($contents, "\n", $end_pos);
            $line_end = ($line_end === false) ? strlen($contents) : ($line_end + 1);

            $contents = substr($contents, 0, $line_start) . substr($contents, $line_end);
            $changed = true;
            $offset = $line_start;
        }

        return array($contents, $changed);
    }

    private function get_htaccess_path() {
        if (!function_exists('get_home_path')) {
            $file_include = ABSPATH . 'wp-admin/includes/file.php';
            if (file_exists($file_include)) {
                require_once $file_include;
            }
        }

        $home_path = function_exists('get_home_path') ? get_home_path() : ABSPATH;
        return trailingslashit($home_path) . '.htaccess';
    }

    private function path_is_writable($path) {
        if (function_exists('wp_is_writable')) {
            return wp_is_writable($path);
        }

        // Conservative fallback when WordPress utility is unavailable.
        return false;
    }

    private function load_login_screen() {
        $login_url = $this->get_core_login_url();

        if ($login_url === '') {
            status_header(404);
            nocache_headers();
            exit;
        }

        $login_url = add_query_arg('vulnity-login', '1', $login_url);
        wp_safe_redirect($login_url, 302);
        exit;
    }

    private function build_login_url($slug) {
        $segments = array_map('rawurlencode', array_filter(explode('/', $slug), 'strlen'));
        $path = implode('/', $segments);

        if ($path === '') {
            $path = self::DEFAULT_LOGIN_SLUG;
        }

        return home_url('/' . $path . '/');
    }

    private function is_internal_login_request() {
        $login_flag_raw = filter_input(INPUT_GET, 'vulnity-login', FILTER_UNSAFE_RAW);
        $login_flag = null !== $login_flag_raw ? sanitize_text_field(function_exists('wp_unslash') ? wp_unslash($login_flag_raw) : $login_flag_raw) : '';

        return $login_flag === '1';
    }

    private function get_core_login_url() {
        remove_filter('login_url', array($this, 'filter_login_url'), 10);

        $login_url = wp_login_url();

        add_filter('login_url', array($this, 'filter_login_url'), 10, 3);

        return $login_url;
    }

    /**
     * Detect web server type.
     *
     * @return string 'apache', 'nginx', 'litespeed', or 'unknown'
     */
    public function detect_server_type() {
        $server_software = vulnity_get_server_var('SERVER_SOFTWARE');
        $server_software_lower = strtolower($server_software);

        if (strpos($server_software_lower, 'apache') !== false) {
            return 'apache';
        }

        if (strpos($server_software_lower, 'nginx') !== false) {
            return 'nginx';
        }

        if (strpos($server_software_lower, 'litespeed') !== false) {
            return 'litespeed';
        }

        return 'unknown';
    }

    /**
     * Generate Nginx configuration snippet based on current settings.
     *
     * @return string Nginx configuration text
     */
    public function get_nginx_config_snippet() {
        $settings = $this->get_settings();
        $lines = array();
        $lines[] = '# Vulnity Security - Nginx Configuration';
        $lines[] = '# Add this to your nginx server block configuration';
        $lines[] = '';

        // Login URL renaming
        if (!empty($settings['rename_login_enabled'])) {
            $slug = $this->sanitize_login_slug($settings['rename_login_slug'] ?? self::DEFAULT_LOGIN_SLUG);
            $normalized_slug = trim($slug, '/');

            if ($normalized_slug !== '') {
                $lines[] = '# Custom login URL';
                $lines[] = "location = /$normalized_slug {";
                $lines[] = '    rewrite ^(.*)$ /wp-login.php?vulnity-login=1 break;';
                $lines[] = '}';
                $lines[] = '';
            }
        }

        // Common paths protection
        if (!empty($settings['protect_common_paths'])) {
            $lines[] = '# Protect common WordPress paths';
            $lines[] = 'location ~* ^/wp-content/(plugins|themes|uploads)/?$ {';
            $lines[] = '    return 403;';
            $lines[] = '}';
            $lines[] = '';
        }

        // Firewall bootstrap (if enabled)
        if (class_exists('Vulnity_Firewall_Manager')) {
            $firewall = Vulnity_Firewall_Manager::get_instance();
            $bootstrap_path = method_exists($firewall, 'get_bootstrap_path') ? $firewall->get_bootstrap_path() : null;

            if ($bootstrap_path && file_exists($bootstrap_path)) {
                $lines[] = '# Firewall bootstrap (add to your php-fpm pool config or nginx.conf)';
                $lines[] = '# In your location ~ \\.php$ block, add:';
                $lines[] = "# fastcgi_param PHP_VALUE \"auto_prepend_file=$bootstrap_path\";";
                $lines[] = '';
            }
        }

        $lines[] = '# Protect secure Vulnity storage';
        $lines[] = 'location ^~ /wp-content/vulnity-data/ {';
        $lines[] = '    deny all;';
        $lines[] = '}';
        $lines[] = '';

        if (count($lines) === 3) {
            $lines[] = '# No active features requiring Nginx configuration';
        }

        return implode("\n", $lines);
    }

    /**
     * Generate Apache .htaccess snippet for documentation purposes.
     *
     * @return string Apache configuration text
     */
    public function get_apache_config_info() {
        $server_type = $this->detect_server_type();

        if ($server_type === 'apache' || $server_type === 'litespeed') {
            return 'Your server is running ' . ucfirst($server_type) . '. .htaccess rules are managed automatically by Vulnity.';
        }

        return 'Your server does not appear to be Apache. .htaccess rules will not work.';
    }
}
