<?php

namespace UltraguardPro\modules\authenticator\ajax;

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

// phpcs:disable WordPress.Security.NonceVerification.Missing -- Two-factor AJAX handlers are registered through the shared protected AJAX framework or verify nonce explicitly.

use UltraguardPro\modules\core\AbstractAjaxHandler;
use UltraguardPro\modules\authenticator\TwoFactorEngine;
use UltraguardPro\modules\authenticator\AuthenticatorModule;

class TwoFactorAjax extends AbstractAjaxHandler {

    protected const NONCE = 'ultraguard_admin_nonce';

    private ?TwoFactorEngine $engine = null;

    public function register(): void {
        $this->register_actions( [
            'ultraguard_2fa_setup_begin'           => [ 'handler' => [ $this, 'handle_setup_begin' ], 'cap' => 'read' ],
            'ultraguard_2fa_setup_verify'          => [ 'handler' => [ $this, 'handle_setup_verify' ], 'cap' => 'read' ],
            'ultraguard_2fa_setup_complete'        => [ 'handler' => [ $this, 'handle_setup_complete' ], 'cap' => 'read' ],
            'ultraguard_2fa_disable'              => [ 'handler' => [ $this, 'handle_disable' ], 'cap' => 'read' ],
            'ultraguard_2fa_regenerate_backup'    => [ 'handler' => [ $this, 'handle_regenerate_backup' ], 'cap' => 'read' ],
            'ultraguard_2fa_verify_login'         => [ 'handler' => [ $this, 'handle_verify_login' ], 'public' => true, 'cap' => '' ],
            'ultraguard_2fa_verify_backup'        => [ 'handler' => [ $this, 'handle_verify_backup' ], 'public' => true, 'cap' => '' ],
            'ultraguard_save_two_factor_settings' => [ $this, 'handle_save_settings' ],
        ] );
    }

    public function handle_setup_begin(): void {
        $this->verify();
        $this->require_pro_feature( 'all_users_2fa', __( 'start the two-factor setup wizard', 'ultraguard-security-suite' ) );

        $user         = wp_get_current_user();
        $secret       = $this->engine()->generate_secret();
        $issuer       = (string) $this->plugin->settings->get_option( 'two_factor_issuer_name', get_bloginfo( 'name' ) );
        $backup_count = (int) $this->plugin->settings->get_option( 'two_factor_backup_code_count', 8 );
        $backup_codes = $this->engine()->generate_backup_codes( $backup_count );

        set_transient( 'ultraguard_2fa_setup_' . get_current_user_id(), [
            'secret'       => $secret,
            'backup_codes' => $backup_codes,
        ], 10 * MINUTE_IN_SECONDS );

        $this->send_success( [
            'secret'       => $secret,
            // We generate the QR code locally so the TOTP secret is never sent to a third-party API.
            'qr_datauri'   => $this->engine()->generate_qr_datauri( $secret, $user->user_login, $issuer ),
            'backup_codes' => $backup_codes,
            'message'      => __( 'Authenticator setup started.', 'ultraguard-security-suite' ),
        ] );
    }

    public function handle_setup_verify(): void {
        $this->verify();
        $this->require_pro_feature( 'all_users_2fa', __( 'verify a two-factor setup code', 'ultraguard-security-suite' ) );

        $code  = sanitize_text_field( wp_unslash( $_POST['code'] ?? '' ) );
        $setup = get_transient( 'ultraguard_2fa_setup_' . get_current_user_id() );

        if ( empty( $setup['secret'] ) ) {
            $this->send_error( __( 'Setup session expired. Start again.', 'ultraguard-security-suite' ), 400, 'setup_expired' );
        }

        if ( ! $this->engine()->verify_code( $setup['secret'], $code, 0 ) ) {
            $this->send_error( __( 'The code was invalid. Please try again.', 'ultraguard-security-suite' ), 403, 'invalid_code' );
        }

        $this->send_success( [ 'message' => __( 'Code verified.', 'ultraguard-security-suite' ) ] );
    }

    public function handle_setup_complete(): void {
        $this->verify();
        $this->require_pro_feature( 'all_users_2fa', __( 'enable two-factor authentication', 'ultraguard-security-suite' ) );

        $table = $this->t( 'totp_credentials' );
        $setup = get_transient( 'ultraguard_2fa_setup_' . get_current_user_id() );

        if ( ! $table || empty( $setup['secret'] ) ) {
            $this->send_error( __( 'Unable to save two-factor setup.', 'ultraguard-security-suite' ), 500, 'setup_missing' );
        }

        $table->upsert_user( get_current_user_id(), [
            'secret_key'      => $this->engine()->encrypt_secret( $setup['secret'] ),
            'backup_codes'    => wp_json_encode( $this->engine()->hash_backup_codes( (array) $setup['backup_codes'] ) ),
            'is_enabled'      => 1,
            'is_enforced'     => $table->is_role_enforced( get_current_user_id(), (array) $this->plugin->settings->get_option( 'two_factor_required_roles', [] ) ) ? 1 : 0,
            'failed_attempts' => 0,
            'locked_until'    => null,
            'last_used_at'    => null,
            'last_used_code'  => '',
        ] );

        delete_transient( 'ultraguard_2fa_setup_' . get_current_user_id() );
        do_action( 'ultraguard_pro/two_factor/enabled', get_current_user_id(), [ 'source' => 'setup_complete' ] );

        $this->send_success( [ 'message' => __( 'Two-factor authentication enabled.', 'ultraguard-security-suite' ) ] );
    }

    public function handle_disable(): void {
        $this->verify();
        $this->require_pro_feature( 'all_users_2fa', __( 'disable two-factor authentication', 'ultraguard-security-suite' ) );

        $password = sanitize_text_field( wp_unslash( $_POST['password'] ?? '' ) );
        $user     = wp_get_current_user();

        if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
            $this->send_error( __( 'Password confirmation failed.', 'ultraguard-security-suite' ), 403, 'password_failed' );
        }

        $table = $this->t( 'totp_credentials' );
        if ( ! $table || ! $table->disable_for_user( $user->ID ) ) {
            $this->send_error( __( 'Unable to disable two-factor authentication.', 'ultraguard-security-suite' ), 500, 'disable_failed' );
        }

        do_action( 'ultraguard_pro/two_factor/disabled', $user->ID, [ 'source' => 'self_service' ] );
        $this->send_success( [ 'message' => __( 'Two-factor authentication disabled.', 'ultraguard-security-suite' ) ] );
    }

    public function handle_regenerate_backup(): void {
        $this->verify();
        $this->require_pro_feature( 'all_users_2fa', __( 'regenerate backup codes', 'ultraguard-security-suite' ) );

        $count = (int) $this->plugin->settings->get_option( 'two_factor_backup_code_count', 8 );
        $codes = $this->engine()->generate_backup_codes( $count );
        $table = $this->t( 'totp_credentials' );

        if ( ! $table || ! $table->replace_backup_codes( get_current_user_id(), $this->engine()->hash_backup_codes( $codes ) ) ) {
            $this->send_error( __( 'Unable to regenerate backup codes.', 'ultraguard-security-suite' ), 500, 'backup_failed' );
        }

        do_action( 'ultraguard_pro/two_factor/backup_regenerated', get_current_user_id(), [ 'source' => 'user_request' ] );

        $this->send_success( [
            'message'      => __( 'Backup codes regenerated.', 'ultraguard-security-suite' ),
            'backup_codes' => $codes,
        ] );
    }

    public function handle_verify_login(): void {
        $this->verify_nonce_only();

        $token   = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) );
        $code    = sanitize_text_field( wp_unslash( $_POST['code'] ?? '' ) );
        $module  = $this->plugin->modules->get( 'authenticator' );
        $pending = $module instanceof AuthenticatorModule ? $module->get_pending_session( $token ) : null;
        $table   = $this->t( 'totp_credentials' );

        if ( ! $pending || ! $table ) {
            $this->send_error( __( 'Verification session expired.', 'ultraguard-security-suite' ), 403, 'session_expired' );
        }

        if ( $table->is_locked( (int) $pending['user_id'] ) ) {
            $this->send_error( __( 'Too many invalid codes. Please wait before trying again.', 'ultraguard-security-suite' ), 423, 'locked' );
        }

        $row    = $table->get_for_user( (int) $pending['user_id'] );
        $secret = $this->engine()->decrypt_secret( (string) ( $row['secret_key'] ?? '' ) );
        $last   = ! empty( $row['last_used_at'] ) ? strtotime( $row['last_used_at'] . ' UTC' ) : 0;

        if ( ! $secret || ! $this->engine()->verify_code( $secret, $code, $last ) ) {
            $attempts = $table->record_failed_attempt( (int) $pending['user_id'] );
            $this->engine()->record_metric( 'failure' );

            if ( $attempts >= 5 ) {
                do_action( 'ultraguard_pro/two_factor/account_locked', (int) $pending['user_id'], [ 'source' => 'totp' ] );
            }

            $this->send_error( __( 'Invalid authentication code.', 'ultraguard-security-suite' ), 403, 'invalid_code' );
        }

        $table->mark_verified( (int) $pending['user_id'], $code );
        $this->engine()->record_metric( 'success' );
        $module->complete_pending_login( $token, ! empty( $pending['remember'] ) );

        $redirect = apply_filters( 'login_redirect', admin_url(), '', get_userdata( (int) $pending['user_id'] ) );
        $this->send_success( [ 'message' => __( 'Verification successful.', 'ultraguard-security-suite' ), 'redirect' => esc_url_raw( $redirect ) ] );
    }

    public function handle_verify_backup(): void {
        $this->verify_nonce_only();

        $token   = sanitize_text_field( wp_unslash( $_POST['token'] ?? '' ) );
        $code    = sanitize_text_field( wp_unslash( $_POST['code'] ?? '' ) );
        $module  = $this->plugin->modules->get( 'authenticator' );
        $pending = $module instanceof AuthenticatorModule ? $module->get_pending_session( $token ) : null;
        $table   = $this->t( 'totp_credentials' );

        if ( ! $pending || ! $table ) {
            $this->send_error( __( 'Verification session expired.', 'ultraguard-security-suite' ), 403, 'session_expired' );
        }

        if ( ! $this->engine()->verify_backup_code( (int) $pending['user_id'], $code ) ) {
            $this->engine()->record_metric( 'failure' );
            $table->record_failed_attempt( (int) $pending['user_id'] );
            $this->send_error( __( 'Invalid backup code.', 'ultraguard-security-suite' ), 403, 'invalid_backup' );
        }

        $table->mark_verified( (int) $pending['user_id'], 'backup' );
        $this->engine()->record_metric( 'success' );
        $module->complete_pending_login( $token, ! empty( $pending['remember'] ) );

        $redirect = apply_filters( 'login_redirect', admin_url(), '', get_userdata( (int) $pending['user_id'] ) );
        $this->send_success( [ 'message' => __( 'Backup code accepted.', 'ultraguard-security-suite' ), 'redirect' => esc_url_raw( $redirect ) ] );
    }

    public function handle_save_settings(): void {
        $this->require_pro_feature( 'all_users_2fa', __( 'save authenticator settings', 'ultraguard-security-suite' ) );

        $data = $this->sanitize_fields( (array) wp_unslash( $_POST ), $this->field_schema() );
        $ok   = $this->plugin->settings->save_module_settings( 'two_factor', $data );

        if ( ! $ok ) {
            $this->send_error( __( 'Failed to save two-factor settings.', 'ultraguard-security-suite' ), 500, 'save_failed' );
        }

        $this->send_success( [ 'message' => __( 'Two-factor settings saved.', 'ultraguard-security-suite' ) ] );
    }

    private function engine(): TwoFactorEngine {
        if ( null === $this->engine ) {
            $this->engine = new TwoFactorEngine( $this->plugin );
        }

        return $this->engine;
    }

    private function field_schema(): array {
        return [
            'two_factor_enabled'              => [ 'type' => 'bool', 'default' => true ],
            'two_factor_allow_self_enroll'   => [ 'type' => 'bool', 'default' => true ],
            'two_factor_required_roles'      => [ 'type' => 'array', 'default' => [], 'options' => array_keys( wp_roles()->get_names() ) ],
            'two_factor_grace_period_days'   => [ 'type' => 'int', 'default' => 7, 'min' => 0, 'max' => 90 ],
            'two_factor_show_setup_prompt'   => [ 'type' => 'bool', 'default' => true ],
            'two_factor_backup_code_count'   => [ 'type' => 'select', 'default' => '8', 'options' => [ '6', '8', '10' ] ],
            'two_factor_email_notifications' => [ 'type' => 'bool', 'default' => true ],
            'two_factor_issuer_name'         => [ 'type' => 'text', 'default' => get_bloginfo( 'name' ) ],
            'two_factor_scheduled_task'      => [ 'type' => 'bool', 'default' => false ],
            'two_factor_task_frequency'      => [ 'type' => 'select', 'default' => 'daily', 'options' => [ 'hourly', 'twicedaily', 'daily' ] ],
        ];
    }
}

// phpcs:enable

