<?php
/**
 * HardeningAjax — AJAX handlers for the WordPress Hardening module.
 *
 * All handlers verify nonce 'ultraguard_admin_nonce' (via AjaxManager) and
 * require 'manage_options'. Settings are persisted in wp_options with the
 * 'ultraguard_hardening_' prefix.
 *
 * @package UltraguardPro\modules\hardening\ajax
 */

namespace UltraguardPro\modules\hardening\ajax;

use UltraguardPro\Plugin;
use UltraguardPro\modules\core\ModuleAjaxSettingsTrait;
use UltraguardPro\support\AjaxResponseBuilder;

if ( ! defined( 'ABSPATH' ) ) { exit; }
// phpcs:disable WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended -- Requests are routed through the shared protected AJAX layer.

class HardeningAjax {

    use ModuleAjaxSettingsTrait;

    /** @var Plugin */
    private Plugin $plugin;

    public function __construct( Plugin $plugin ) {
        $this->plugin = $plugin;
    }

    public function register(): void {
        $ajax    = $this->plugin->ajax;
        $actions = [
            'ultraguard_hardening_save'        => [ $this, 'handle_save' ],
            'ultraguard_hardening_run_check'   => [ $this, 'handle_run_check' ],
            'ultraguard_hardening_apply_all'   => [ $this, 'handle_apply_all' ],
            'ultraguard_hardening_reset'       => [ $this, 'handle_reset' ],
        ];
        foreach ( $actions as $action => $cb ) {
            $ajax->register_action( $action, $cb );
        }
    }

    // =========================================================================
    // HANDLERS
    // =========================================================================

    /**
     * Save individual hardening setting toggles.
     * Expects POST: settings[] — array of key => value pairs.
     */
    /**
     * Field schema for all hardening toggles.
     * Stored under the 'hardening_' prefix in the ultraguard_hardening_settings blob.
     */
    private function field_schema(): array {
        $schema = [];
        foreach ( $this->allowed_keys() as $key => $type ) {
            $schema[ 'hardening_' . $key ] = [ 'type' => $type === 'bool' ? 'bool' : 'text' ];
        }
        return $schema;
    }

    public function handle_save(): void {
        $this->cap();

        // The form renders toggles as top-level POST fields with the full
        // 'hardening_' prefix (e.g. hardening_disable_xmlrpc=1).
        // sanitize_fields() reads those keys directly from the raw POST array.
        $data = $this->sanitize_fields( (array) wp_unslash( $_POST ), $this->field_schema() ); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Settings save is capability-gated and routed through the protected admin AJAX flow.
        $ok   = $this->plugin->settings->save_module_settings( 'hardening', $data );

        $this->apply_hardening();

        /* translators: %d: integer */
        $msg = sprintf( __( '%d hardening setting(s) saved and applied.', 'ultraguard-security-suite' ), count( $data ) );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'saved', count( $data ) )
            ->addNotification( __( 'Settings saved!', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    /**
     * Run a live hardening audit and return results.
     */
    public function handle_run_check(): void {
        $this->cap();
        $results = $this->run_hardening_checks();
        $score   = $this->calculate_score( $results );

        $grade = $this->score_to_grade( $score );
        /* translators: %d: integer */
        $msg   = sprintf( __( 'Hardening audit complete — score: %d/100', 'ultraguard-security-suite' ), $score );
        $status = $score >= 75 ? 'success' : ( $score >= 50 ? 'warning' : 'error' );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'checks', $results )
            ->addData( 'score', $score )
            ->addData( 'grade', $grade )
            ->addNotification( $msg, $status )
            ->updateText( '#ultraguard-hardening-score', (string) $score )
            ->updateText( '#ultraguard-hardening-grade', $grade )
            ->send();
    }

    /**
     * Enable ALL hardening settings at once.
     */
    public function handle_apply_all(): void {
        $this->cap();

        $data = [];
        foreach ( $this->allowed_keys() as $key => $type ) {
            if ( $type === 'bool' ) {
                $data[ 'hardening_' . $key ] = true;
            }
        }
        $this->plugin->settings->save_module_settings( 'hardening', $data );
        $this->apply_hardening();

        AjaxResponseBuilder::make()
            ->success( __( 'All hardening settings enabled and applied.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'All hardening settings enabled and applied.', 'ultraguard-security-suite' ), 'success' )
            ->addReload( 1000 )
            ->send();
    }

    /**
     * Reset all hardening settings to defaults (disabled).
     */
    public function handle_reset(): void {
        $this->cap();

        $data = [];
        foreach ( $this->allowed_keys() as $key => $type ) {
            $data[ 'hardening_' . $key ] = ( $type === 'bool' ) ? false : '';
        }
        $this->plugin->settings->save_module_settings( 'hardening', $data );
        $this->apply_hardening();

        AjaxResponseBuilder::make()
            ->success( __( 'Hardening settings reset to defaults.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Hardening settings reset to defaults.', 'ultraguard-security-suite' ), 'info' )
            ->addReload( 1000 )
            ->send();
    }

    // =========================================================================
    // HARDENING ENGINE
    // =========================================================================

    /**
     * Apply all currently-enabled hardening settings to the site.
     * Uses WordPress hooks and wp-config injection where needed.
     */
    private function apply_hardening(): void {
        $s = fn( string $k, $d = false ) => $this->plugin->settings->get_option( 'hardening_' . $k, $d );

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

        // ── REST API unauthenticated access ──────────────────────────────────
        if ( $s( 'disable_rest_unauthenticated' ) ) {
            add_filter( 'rest_authentication_errors', [ $this, 'block_rest_unauthenticated' ], 99 );
        }

        // ── Remove WP version from <head> ────────────────────────────────────
        if ( $s( 'remove_wp_version' ) ) {
            remove_action( 'wp_head', 'wp_generator' );
            add_filter( 'the_generator', '__return_empty_string', 999 );
        }

        // ── Disable 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' ] );
        }

        // ── Remove pingbacks/trackbacks ──────────────────────────────────────
        if ( $s( 'disable_pingbacks' ) ) {
            add_filter( 'xmlrpc_methods', [ $this, 'remove_pingback_method' ] );
            add_filter( 'bloginfo_url', [ $this, 'remove_pingback_url' ], 10, 2 );
        }

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

        // ── Remove wp-embed script ────────────────────────────────────────────
        if ( $s( 'disable_embeds' ) ) {
            add_action( 'wp_footer', [ $this, 'dequeue_embed_scripts' ], 99 );
        }

        // ── Force HTTPS for admin ────────────────────────────────────────────
        if ( $s( 'force_ssl_admin' ) && ! is_ssl() ) {
            // Already handled by FORCE_SSL_ADMIN constant in wp-config.php
        }

        // ── Hide login errors ────────────────────────────────────────────────
        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
            );
        }

        // ── Disable directory browsing ────────────────────────────────────────
        // Handled by .htaccess module's "disable_directory_listing" rule

        do_action( 'ultraguard_hardening_applied', $this->plugin->settings );
    }

    // ── 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 {
        if ( ! is_admin() && isset( $_GET['author'] ) && is_numeric( $_GET['author'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only author-enumeration guard on frontend requests.
            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 {
        if ( 'pingback_url' === $show ) { return ''; }
        return $output;
    }

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

    // =========================================================================
    // AUDIT CHECKS
    // =========================================================================

    private function run_hardening_checks(): array {
        $checks = [];

        // 1. XML-RPC
        $xmlrpc_enabled = apply_filters( 'xmlrpc_enabled', true );
        $checks['xmlrpc'] = [
            'label'   => __( 'XML-RPC disabled', 'ultraguard-security-suite' ),
            'pass'    => ! $xmlrpc_enabled,
            'fix_key' => 'disable_xmlrpc',
            'severity'=> 'high',
            'detail'  => $xmlrpc_enabled
                ? __( 'XML-RPC is enabled — disable it unless you specifically need it.', 'ultraguard-security-suite' )
                : __( 'XML-RPC is disabled.', 'ultraguard-security-suite' ),
        ];

        // 2. Default admin username
        $admin_exists = get_user_by( 'login', 'admin' ) !== false;
        $checks['admin_username'] = [
            'label'   => __( 'No "admin" username', 'ultraguard-security-suite' ),
            'pass'    => ! $admin_exists,
            'severity'=> 'critical',
            'detail'  => $admin_exists
                ? __( 'A user named "admin" exists — attackers target this username first. Rename it.', 'ultraguard-security-suite' )
                : __( 'No user named "admin" found.', 'ultraguard-security-suite' ),
        ];

        // 3. WordPress version exposure
        $generator_in_head = has_action( 'wp_head', 'wp_generator' );
        $checks['wp_version'] = [
            'label'   => __( 'WordPress version hidden', 'ultraguard-security-suite' ),
            'pass'    => ! $generator_in_head,
            'fix_key' => 'remove_wp_version',
            'severity'=> 'medium',
            'detail'  => $generator_in_head
                ? __( 'WordPress version is visible in <head> and RSS feeds — hide it.', 'ultraguard-security-suite' )
                : __( 'WordPress version is not exposed in <head>.', 'ultraguard-security-suite' ),
        ];

        // 4. DISALLOW_FILE_EDIT
        $file_edit_disabled = defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT;
        $checks['file_edit'] = [
            'label'   => __( 'File editing disabled', 'ultraguard-security-suite' ),
            'pass'    => $file_edit_disabled,
            'fix_key' => 'disallow_file_edit',
            'severity'=> 'high',
            'detail'  => ! $file_edit_disabled
                ? __( 'DISALLOW_FILE_EDIT is not set — admins can edit plugin/theme files from wp-admin.', 'ultraguard-security-suite' )
                : __( 'File editing is disabled via DISALLOW_FILE_EDIT.', 'ultraguard-security-suite' ),
        ];

        // 5. Debug mode
        $debug_on = defined( 'WP_DEBUG' ) && WP_DEBUG;
        $checks['debug_mode'] = [
            'label'   => __( 'Debug mode off', 'ultraguard-security-suite' ),
            'pass'    => ! $debug_on,
            'severity'=> 'high',
            'detail'  => $debug_on
                ? __( 'WP_DEBUG is true — this exposes error messages to visitors on production sites.', 'ultraguard-security-suite' )
                : __( 'Debug mode is off.', 'ultraguard-security-suite' ),
        ];

        // 6. wp-config.php location
        $config_in_root = file_exists( ABSPATH . 'wp-config.php' );
        $checks['wp_config_location'] = [
            'label'   => __( 'wp-config.php not in web root', 'ultraguard-security-suite' ),
            'pass'    => ! $config_in_root,
            'severity'=> 'medium',
            'detail'  => $config_in_root
                ? __( 'wp-config.php is in the web root — move it one level up or protect it via .htaccess.', 'ultraguard-security-suite' )
                : __( 'wp-config.php is not directly in the web root.', 'ultraguard-security-suite' ),
        ];

        // 7. SSL
        $ssl_on = is_ssl() || ( defined( 'FORCE_SSL_ADMIN' ) && FORCE_SSL_ADMIN );
        $checks['ssl'] = [
            'label'   => __( 'SSL / HTTPS active', 'ultraguard-security-suite' ),
            'pass'    => $ssl_on,
            'fix_key' => 'force_ssl_admin',
            'severity'=> 'high',
            'detail'  => ! $ssl_on
                ? __( 'The site is not served over HTTPS. Enable SSL immediately.', 'ultraguard-security-suite' )
                : __( 'HTTPS is active.', 'ultraguard-security-suite' ),
        ];

        // 8. User enumeration
        $enum_check = $this->check_user_enumeration();
        $checks['user_enumeration'] = [
            'label'   => __( 'User enumeration blocked', 'ultraguard-security-suite' ),
            'pass'    => $enum_check,
            'fix_key' => 'disable_user_enumeration',
            'severity'=> 'medium',
            'detail'  => ! $enum_check
                ? __( '/?author=1 redirects to the author archive, leaking usernames.', 'ultraguard-security-suite' )
                : __( 'User enumeration via ?author=N is blocked.', 'ultraguard-security-suite' ),
        ];

        // 9. RSD/WLW meta tags
        $rsd_on = has_action( 'wp_head', 'rsd_link' );
        $checks['rsd_link'] = [
            'label'   => __( 'RSD / WLW meta tags removed', 'ultraguard-security-suite' ),
            'pass'    => ! $rsd_on,
            'fix_key' => 'remove_rsd_link',
            'severity'=> 'low',
            'detail'  => $rsd_on
                ? __( 'Really Simple Discovery and Windows Live Writer meta tags expose service endpoints.', 'ultraguard-security-suite' )
                : __( 'RSD / WLW meta tags are removed.', 'ultraguard-security-suite' ),
        ];

        // 10. File permissions
        $config_perms = file_exists( ABSPATH . 'wp-config.php' )
            ? substr( sprintf( '%o', fileperms( ABSPATH . 'wp-config.php' ) ), -4 )
            : '????';
        $perms_ok = in_array( $config_perms, [ '0400', '0440', '0600', '0640', '0644' ], true );
        $checks['file_permissions'] = [
            'label'   => __( 'wp-config.php permissions secure', 'ultraguard-security-suite' ),
            'pass'    => $perms_ok,
            'severity'=> 'high',
            'detail'  => ! $perms_ok
                /* translators: %s: string */
                ? sprintf( __( 'wp-config.php permissions are %s — should be 640 or 600.', 'ultraguard-security-suite' ), $config_perms )
                /* translators: %s: string */
                : sprintf( __( 'wp-config.php permissions are %s (OK).', 'ultraguard-security-suite' ), $config_perms ),
        ];

        return apply_filters( 'ultraguard_hardening_checks', $checks );
    }

    private function check_user_enumeration(): bool {
        // Check if our own block is in place (filter registered)
        // We test by checking if the hook is set
        return has_action( 'template_redirect', [ $this, 'block_author_enumeration' ] ) !== false;
    }

    private function calculate_score( array $checks ): int {
        $weights = [ 'critical' => 25, 'high' => 15, 'medium' => 8, 'low' => 4 ];
        $total   = 0;
        $earned  = 0;
        foreach ( $checks as $c ) {
            $w      = $weights[ $c['severity'] ?? 'low' ] ?? 4;
            $total += $w;
            if ( $c['pass'] ) { $earned += $w; }
        }
        return $total > 0 ? (int) round( ( $earned / $total ) * 100 ) : 0;
    }

    private function score_to_grade( int $score ): string {
        if ( $score >= 90 ) { return 'A'; }
        if ( $score >= 75 ) { return 'B'; }
        if ( $score >= 60 ) { return 'C'; }
        if ( $score >= 40 ) { return 'D'; }
        return 'F';
    }

    // =========================================================================
    // PRIVATE HELPERS
    // =========================================================================

    private function cap(): void {
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            AjaxResponseBuilder::make()->permissionError()->send( 403 );
        }
    }

    private function allowed_keys(): array {
        return [
            'disable_xmlrpc'                => 'bool',
            'disable_rest_unauthenticated'  => 'bool',
            'remove_wp_version'             => 'bool',
            'disallow_file_edit'            => 'bool',
            'disallow_file_mods'            => 'bool',
            'disable_user_enumeration'      => 'bool',
            'disable_pingbacks'             => 'bool',
            'remove_rsd_link'               => 'bool',
            'disable_embeds'                => 'bool',
            'force_ssl_admin'               => 'bool',
            'hide_login_errors'             => 'bool',
            'disable_application_passwords' => 'bool',
        ];
    }

    private function cast( $value, string $type ) {
        if ( $type === 'bool' ) { return filter_var( $value, FILTER_VALIDATE_BOOLEAN ); }
        if ( $type === 'int' )  { return absint( $value ); }
        return sanitize_text_field( (string) $value );
    }
}
