<?php
/**
 * HardeningModule — WordPress Security Hardening module.
 *
 * Applies best-practice security hardening to the WordPress installation:
 *   • Disable XML-RPC
 *   • Require auth for REST API
 *   • Hide WordPress version
 *   • Disable user enumeration (?author=N)
 *   • Remove pingbacks / RSD / WLW meta
 *   • Disable oEmbed wp-embed.js
 *   • Force SSL for admin
 *   • Hide login error details
 *   • Disable Application Passwords
 *   • Live hardening audit page with score/grade
 *
 * Architecture:
 *   • FREE module — requires_pro() returns false.
 *   • All settings stored under the 'hardening_' prefix.
 *   • AJAX via HardeningAjax (registered through AjaxManager → admin nonce).
 *   • Single admin page at priority 25.
 *
 * @package UltraguardPro\modules\hardening
 * @since   6.6.0
 */

namespace UltraguardPro\modules\hardening;

use UltraguardPro\modules\core\AbstractModule;

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

class HardeningModule extends AbstractModule {

    public function get_id(): string { return 'hardening'; }

    /** Free module — available to all users. */
    protected function requires_pro(): bool { return false; }

    /**
     * Register our settings prefix immediately so Settings::get_option() can
     * resolve 'hardening_*' keys before boot() is called.
     */
    public function __construct( \UltraguardPro\Plugin $plugin ) {
        parent::__construct( $plugin );
        $this->plugin->settings->register_module_prefix( 'hardening_', 'hardening' );
    }

    public function boot(): void {
        // 1. AJAX handlers (registered through AjaxManager → ultraguard_admin_nonce)
        ( new ajax\HardeningAjax( $this->plugin ) )->register();

        // 2. Admin page
        add_action( 'ultraguard_register_admin_pages', [ $this, 'register_pages' ], 25, 1 );

        // 3. Apply hardening rules on every request (early, before headers sent)
        add_action( 'init', [ $this, 'apply_hardening' ], 5 );

        // 4. Disable Application Passwords if configured
        add_filter( 'wp_is_application_passwords_available', [ $this, 'maybe_disable_app_passwords' ] );
        add_filter( 'ultraguard_pro/compliance/available_sections', [ $this, 'add_compliance_sections' ] );
        add_filter( 'ultraguard_pro/compliance/collect_data', [ $this, 'add_compliance_data' ], 10, 4 );

        do_action( 'ultraguard_pro/hardening/booted', $this );
    }

    // ── Admin pages ────────────────────────────────────────────────────────────

    public function register_pages( $admin ): void {
        $admin->register_page( [
            'id'              => 'hardening',
            'page_title'      => __( 'WordPress Hardening', 'ultraguard-security-suite' ),
            'menu_title'      => __( 'Hardening', 'ultraguard-security-suite' ),
            'capability'      => 'manage_options',
            'scheme_callback' => [ pages\HardeningPage::class, $this->plugin ],
            'is_parent'       => true,
            'priority'        => 25,
            'group'           => 'Protection',
            'icon'            => 'dashicons-shield-alt',
        ] );
    }

    public function add_compliance_sections( array $sections ): array {
        $sections['hardening_checks'] = __( 'Hardening Checks', 'ultraguard-security-suite' );
        return $sections;
    }

    public function add_compliance_data( array $sections, string $type, array $options, array $meta ): array {
        $requested = isset( $options['sections'] ) && is_array( $options['sections'] ) ? $options['sections'] : [];
        if ( $requested && ! in_array( 'hardening_checks', $requested, true ) ) {
            return $sections;
        }

        $checks = [
            'Disable XML-RPC'                  => $this->option_enabled( 'hardening_disable_xmlrpc' ),
            'Require auth for REST API'        => $this->option_enabled( 'hardening_disable_rest_unauthenticated' ),
            'Remove WordPress version'         => $this->option_enabled( 'hardening_remove_wp_version' ),
            'Disable user enumeration'         => $this->option_enabled( 'hardening_disable_user_enumeration' ),
            'Disable pingbacks'                => $this->option_enabled( 'hardening_disable_pingbacks' ),
            'Remove RSD and WLW links'         => $this->option_enabled( 'hardening_remove_rsd_link' ),
            'Disable embeds'                   => $this->option_enabled( 'hardening_disable_embeds' ),
            'Force SSL for admin'              => $this->option_enabled( 'hardening_force_ssl_admin' ),
            'Hide login errors'                => $this->option_enabled( 'hardening_hide_login_errors' ),
            'Disable application passwords'    => $this->option_enabled( 'hardening_disable_application_passwords' ),
        ];

        $enabled = array_filter( $checks );

        $sections['hardening_checks'] = [
            'title'   => __( 'Hardening Checks', 'ultraguard-security-suite' ),
            'content' => [
                'summary' => [
                    'enabled_checks' => count( $enabled ),
                    'total_checks'   => count( $checks ),
                    'score_percent'  => count( $checks ) > 0 ? (int) round( ( count( $enabled ) / count( $checks ) ) * 100 ) : 0,
                ],
                'checks'  => array_map(
                    static fn( string $label, bool $status ): array => [
                        'requirement' => $label,
                        'status'      => $status ? 'pass' : 'partial',
                    ],
                    array_keys( $checks ),
                    $checks
                ),
            ],
        ];

        return $sections;
    }

    // ── Hardening engine ───────────────────────────────────────────────────────

    /**
     * Apply all enabled hardening settings. Fires on 'init' priority 5 so
     * settings take effect before any output or REST routing.
     */
    public function apply_hardening(): void {
        $s = fn( string $k, $d = false ) => $this->option( 'hardening_' . $k, $d );

        // XML-RPC
        if ( $s( 'disable_xmlrpc' ) ) {
            add_filter( 'xmlrpc_enabled', '__return_false', 999 );
        }

        // REST API — require authentication
        if ( $s( 'disable_rest_unauthenticated' ) ) {
            add_filter( 'rest_authentication_errors', [ $this, 'block_rest_unauthenticated' ], 99 );
        }

        // Remove WP version from <head>, RSS and HTTP headers
        if ( $s( 'remove_wp_version' ) ) {
            remove_action( 'wp_head', 'wp_generator' );
            add_filter( 'the_generator', '__return_empty_string', 999 );
            // Also strip from HTTP headers emitted by some hosts
            add_filter( 'style_loader_src',  [ $this, 'remove_version_from_src' ], 9999 );
            add_filter( 'script_loader_src', [ $this, 'remove_version_from_src' ], 9999 );
        }

        // User enumeration
        if ( $s( 'disable_user_enumeration' ) ) {
            add_action( 'template_redirect', [ $this, 'block_author_enumeration' ], 1 );
            add_filter( 'rest_endpoints', [ $this, 'disable_rest_user_endpoints' ] );
        }

        // Pingbacks / trackbacks
        if ( $s( 'disable_pingbacks' ) ) {
            add_filter( 'xmlrpc_methods',    [ $this, 'remove_pingback_method' ] );
            add_filter( 'bloginfo_url',      [ $this, 'remove_pingback_url' ], 10, 2 );
            add_filter( 'pre_option_default_pingback_flag', '__return_zero', 999 );
        }

        // RSD / WLW meta
        if ( $s( 'remove_rsd_link' ) ) {
            remove_action( 'wp_head', 'rsd_link' );
            remove_action( 'wp_head', 'wlwmanifest_link' );
        }

        // oEmbed
        if ( $s( 'disable_embeds' ) ) {
            add_action( 'wp_footer', [ $this, 'dequeue_embed_scripts' ], 99 );
            remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
        }

        // Force SSL admin
        if ( $s( 'force_ssl_admin' ) && ! defined( 'FORCE_SSL_ADMIN' ) ) {
            define( 'FORCE_SSL_ADMIN', true );
        }

        // Generic login error
        if ( $s( 'hide_login_errors' ) ) {
            add_filter( 'login_errors', '__return_empty_string', 999 );
            add_filter(
                'lost_password_errors',
                static fn( $errors ) => new \WP_Error(
                    'invalidcombo',
                    __( '<strong>Error:</strong> The email address entered does not match any account.', 'ultraguard-security-suite' )
                ),
                999
            );
        }
    }

    // ── Hook callbacks ─────────────────────────────────────────────────────────

    public function block_rest_unauthenticated( $errors ) {
        if ( ! empty( $errors ) ) { return $errors; }
        if ( ! is_user_logged_in() ) {
            return new \WP_Error(
                'rest_not_logged_in',
                __( 'REST API access requires authentication.', 'ultraguard-security-suite' ),
                [ 'status' => 401 ]
            );
        }
        return $errors;
    }

    public function block_author_enumeration(): void {
        // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Public request inspection for author enumeration is intentionally read-only.
        if ( ! is_admin() && isset( $_GET['author'] ) && is_numeric( $_GET['author'] ) ) {
            wp_safe_redirect( home_url( '/' ), 301 );
            exit;
        }
    }

    public function disable_rest_user_endpoints( array $endpoints ): array {
        if ( ! is_user_logged_in() ) {
            if ( isset( $endpoints['/wp/v2/users'] ) ) {
                unset( $endpoints['/wp/v2/users'] );
            }
            if ( isset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] ) ) {
                unset( $endpoints['/wp/v2/users/(?P<id>[\d]+)'] );
            }
        }

        return $endpoints;
    }

    public function remove_pingback_method( array $methods ): array {
        unset( $methods['pingback.ping'] );
        return $methods;
    }

    public function remove_pingback_url( string $output, string $show ): string {
        return 'pingback_url' === $show ? '' : $output;
    }

    public function dequeue_embed_scripts(): void {
        wp_dequeue_script( 'wp-embed' );
    }

    public function remove_version_from_src( string $src ): string {
        if ( strpos( $src, 'ver=' ) !== false ) {
            $src = remove_query_arg( 'ver', $src );
        }
        return $src;
    }

    public function maybe_disable_app_passwords( bool $available ): bool {
        if ( $this->option( 'hardening_disable_application_passwords', false ) ) {
            return false;
        }
        return $available;
    }
}
