<?php
/**
 * SecurityHeadersAjax — AJAX handlers for the Security Headers module.
 *
 * All handlers are registered via AjaxManager and verify 'ultraguard_admin_nonce'.
 *
 * @package UltraguardPro\modules\security_headers\ajax
 */

namespace UltraguardPro\modules\security_headers\ajax;
// phpcs:disable WordPress.Security.NonceVerification.Recommended,WordPress.Security.NonceVerification.Missing -- Requests are routed through the shared protected AJAX layer.

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

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

class SecurityHeadersAjax {

    use ModuleAjaxSettingsTrait;

    private const INTERNAL_REQUEST_ACTION = 'security-headers-test';

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

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

    public function register(): void {
        $ajax    = $this->plugin->ajax;
        $actions = [
            'ultraguard_headers_save'      => [ $this, 'handle_save' ],
            'ultraguard_headers_test'      => [ $this, 'handle_test' ],
            'ultraguard_headers_preview'   => [ $this, 'handle_preview' ],
            'ultraguard_headers_reset'     => [ $this, 'handle_reset' ],
        ];
        foreach ( $actions as $action => $cb ) {
            $ajax->register_action( $action, $cb );
        }
    }

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

    /**
     * Field schema built dynamically from allowed_keys().
     * allowed_keys() is the single source of truth for what this module owns.
     */
    private function field_schema(): array {
        $schema = [];
        foreach ( $this->allowed_keys() as $key => $type ) {
            $schema[ 'headers_' . $key ] = match ( $type ) {
                'bool'   => [ 'type' => 'bool' ],
                'int'    => [ 'type' => 'int', 'default' => 0 ],
                default  => [ 'type' => 'text', 'default' => '' ],
            };
        }
        return $schema;
    }

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

        // The form renders fields as top-level POST fields with the full
        // 'headers_' prefix (e.g. headers_xframe_enabled=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.Recommended -- Nonce is verified by the shared abstract AJAX handler before this method executes.
        $ok   = $this->plugin->settings->save_module_settings( 'security_headers', $data );

        AjaxResponseBuilder::make()
            ->success( __( 'Security header settings saved.', 'ultraguard-security-suite' ) )
            ->addNotification( __( 'Headers saved!', 'ultraguard-security-suite' ), 'success' )
            ->send();
    }

    /**
     * Test which headers the live site currently sends.
     * Makes a loopback HTTP request and reads response headers.
     */
    public function handle_test(): void {
        $this->cap();

        $url      = home_url( '/' );
        $response = wp_remote_head( $url, [
            'timeout'   => 10,
            'sslverify' => false,
            'user-agent'=> 'UltraGuard Security Headers Test',
            'headers'   => [
                'X-UltraGuard-Internal'       => self::INTERNAL_REQUEST_ACTION,
                'X-UltraGuard-Internal-Token' => $this->get_internal_request_token(),
            ],
        ] );

        if ( is_wp_error( $response ) ) {
            AjaxResponseBuilder::make()
                ->error( $response->get_error_message() )
                ->addNotification( $response->get_error_message(), 'error' )
                ->send();
            return;
        }

        $headers_sent = wp_remote_retrieve_headers( $response );
        $expected     = $this->expected_headers();
        $results      = [];

        foreach ( $expected as $name => $label ) {
            $sent  = $headers_sent->offsetGet( strtolower( $name ) );
            $results[] = [
                'header'  => $name,
                'label'   => $label,
                'sent'    => ! empty( $sent ),
                'value'   => $sent ?: __( '(not sent)', 'ultraguard-security-suite' ),
            ];
        }

        $pass_count = count( array_filter( $results, fn( $r ) => $r['sent'] ) );
        $score      = count( $expected ) > 0 ? (int) round( $pass_count / count( $expected ) * 100 ) : 0;

        $grade  = $this->score_to_grade( $score );
        /* translators: %1$d: integer, %2$d: integer, %3$d: integer */
        $msg    = sprintf( __( 'Header test complete: %1$d/%2$d headers present (score %3$d/100)', 'ultraguard-security-suite' ), $pass_count, count( $expected ), $score );
        $status = $score >= 75 ? 'success' : ( $score >= 50 ? 'warning' : 'error' );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'results', $results )
            ->addData( 'score', $score )
            ->addData( 'pass_count', $pass_count )
            ->addData( 'total', count( $expected ) )
            ->addData( 'grade', $grade )
            ->addNotification( $msg, $status )
            ->updateText( '#ultraguard-headers-test-score', (string) $score )
            ->updateText( '#ultraguard-headers-test-grade', $grade )
            ->updateText( '#ultraguard-headers-pass-count', $pass_count . '/' . count( $expected ) )
            ->send();
    }

    /**
     * Return a preview of the header values that will be sent.
     */
    public function handle_preview(): void {
        $this->cap();
        $s       = $this->plugin->settings;
        $headers = $this->build_headers( $s );

        /* translators: %d: integer */
        $msg = sprintf( __( '%d security headers will be sent.', 'ultraguard-security-suite' ), count( $headers ) );
        AjaxResponseBuilder::make()
            ->success( $msg )
            ->addData( 'headers', $headers )
            ->addData( 'count', count( $headers ) )
            ->addNotification( $msg, 'info' )
            ->updateText( '#ultraguard-headers-preview-count', (string) count( $headers ) )
            ->send();
    }

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

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

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

    // =========================================================================
    // HELPERS
    // =========================================================================

    /**
     * Build the array of header name => value pairs to emit.
     */
    public function build_headers( $settings ): array {
        $s       = fn( string $k, $d = null ) => $settings->get_option( 'headers_' . $k, $d );
        $headers = [];

        // X-Frame-Options
        if ( $s( 'xframe_enabled', true ) ) {
            $xframe = sanitize_text_field( $s( 'xframe_value', 'SAMEORIGIN' ) );
            if ( in_array( $xframe, [ 'DENY', 'SAMEORIGIN' ], true ) ) {
                $headers['X-Frame-Options'] = $xframe;
            }
        }

        // X-Content-Type-Options
        if ( $s( 'xcontent_type_enabled', true ) ) {
            $headers['X-Content-Type-Options'] = 'nosniff';
        }

        // X-XSS-Protection (legacy browsers)
        if ( $s( 'xxss_enabled', true ) ) {
            $headers['X-XSS-Protection'] = '1; mode=block';
        }

        // Referrer-Policy
        if ( $s( 'referrer_policy_enabled', true ) ) {
            $valid  = [ 'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url' ];
            $policy = sanitize_text_field( $s( 'referrer_policy_value', 'strict-origin-when-cross-origin' ) );
            if ( in_array( $policy, $valid, true ) ) {
                $headers['Referrer-Policy'] = $policy;
            }
        }

        // Permissions-Policy
        if ( $s( 'permissions_policy_enabled', true ) ) {
            $parts = [];
            $features = [ 'camera', 'microphone', 'geolocation', 'payment', 'usb', 'fullscreen', 'autoplay', 'gyroscope', 'accelerometer' ];
            foreach ( $features as $f ) {
                $val = sanitize_text_field( $s( 'permissions_' . $f, '()' ) );
                $parts[] = $f . '=' . $val;
            }
            if ( $parts ) {
                $headers['Permissions-Policy'] = implode(', ', $parts);
            }
        }

        // Strict-Transport-Security (HSTS)
        if ( is_ssl() && $s( 'hsts_enabled', false ) ) {
            $max_age        = (int) $s( 'hsts_max_age', 31536000 );
            $include_sub    = (bool) $s( 'hsts_include_subdomains', true );
            $preload        = (bool) $s( 'hsts_preload', false );
            $hsts           = 'max-age=' . $max_age;
            if ( $include_sub ) { $hsts .= '; includeSubDomains'; }
            if ( $preload )     { $hsts .= '; preload'; }
            $headers['Strict-Transport-Security'] = $hsts;
        }

        // Cross-Origin-Opener-Policy
        if ( $s( 'coop_enabled', false ) ) {
            $valid = [ 'unsafe-none', 'same-origin-allow-popups', 'same-origin' ];
            $coop  = sanitize_text_field( $s( 'coop_value', 'same-origin' ) );
            if ( in_array( $coop, $valid, true ) ) {
                $headers['Cross-Origin-Opener-Policy'] = $coop;
            }
        }

        // Cross-Origin-Embedder-Policy
        if ( $s( 'coep_enabled', false ) ) {
            $valid = [ 'unsafe-none', 'require-corp', 'credentialless' ];
            $coep  = sanitize_text_field( $s( 'coep_value', 'require-corp' ) );
            if ( in_array( $coep, $valid, true ) ) {
                $headers['Cross-Origin-Embedder-Policy'] = $coep;
            }
        }

        // Content-Security-Policy
        if ( $s( 'csp_enabled', false ) ) {
            $csp_value = sanitize_textarea_field( wp_unslash( $s( 'csp_value', '' ) ) );
            if ( $csp_value ) {
                $header_name = $s( 'csp_report_only', false )
                    ? 'Content-Security-Policy-Report-Only'
                    : 'Content-Security-Policy';
                $headers[ $header_name ] = $csp_value;
            }
        }

        return apply_filters( 'ultraguard_security_headers', $headers );
    }

    private function expected_headers(): array {
        return [
            'X-Frame-Options'           => __( 'X-Frame-Options (clickjacking)', 'ultraguard-security-suite' ),
            'X-Content-Type-Options'    => __( 'X-Content-Type-Options (MIME sniffing)', 'ultraguard-security-suite' ),
            'X-XSS-Protection'          => __( 'X-XSS-Protection (legacy XSS filter)', 'ultraguard-security-suite' ),
            'Referrer-Policy'           => __( 'Referrer-Policy (data leakage)', 'ultraguard-security-suite' ),
            'Permissions-Policy'        => __( 'Permissions-Policy (feature control)', 'ultraguard-security-suite' ),
            'Strict-Transport-Security' => __( 'Strict-Transport-Security (HSTS)', 'ultraguard-security-suite' ),
            'Content-Security-Policy'   => __( 'Content-Security-Policy (XSS / injection)', 'ultraguard-security-suite' ),
        ];
    }

    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 function cap(): void {
        if ( ! \UltraguardPro\ultraguard_current_user_can_manage() ) {
            AjaxResponseBuilder::make()->permissionError()->send( 403 );
        }
    }

    private function get_internal_request_token(): string {
        return wp_hash( self::INTERNAL_REQUEST_ACTION . '|' . untrailingslashit( home_url() ), 'auth' );
    }

    private function allowed_keys(): array {
        return [
            'xframe_enabled'               => 'bool',
            'xframe_value'                 => 'string',
            'xcontent_type_enabled'        => 'bool',
            'xxss_enabled'                 => 'bool',
            'referrer_policy_enabled'      => 'bool',
            'referrer_policy_value'        => 'string',
            'permissions_policy_enabled'   => 'bool',
            'permissions_camera'           => 'string',
            'permissions_microphone'       => 'string',
            'permissions_geolocation'      => 'string',
            'permissions_payment'          => 'string',
            'permissions_usb'              => 'string',
            'permissions_fullscreen'       => 'string',
            'permissions_autoplay'         => 'string',
            'permissions_gyroscope'        => 'string',
            'permissions_accelerometer'    => 'string',
            'hsts_enabled'                 => 'bool',
            'hsts_max_age'                 => 'int',
            'hsts_include_subdomains'      => 'bool',
            'hsts_preload'                 => 'bool',
            'coop_enabled'                 => 'bool',
            'coop_value'                   => 'string',
            'coep_enabled'                 => 'bool',
            'coep_value'                   => 'string',
            'csp_enabled'                  => 'bool',
            'csp_report_only'              => 'bool',
            'csp_value'                    => 'string',
        ];
    }

    private function default_on_keys(): array {
        return [
            'xframe_enabled', 'xcontent_type_enabled', 'xxss_enabled',
            'referrer_policy_enabled', 'permissions_policy_enabled',
        ];
    }

    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 );
    }
}
