<?php
/**
 * TOTP provider integration for core 2FA flow.
 *
 * @package Polanger_Dashboard_Tools
 */

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

class PDT_Authenticator_Provider {

  /**
   * TOTP service.
   *
   * @var PDT_Authenticator_TOTP
   */
  private $totp;

  /**
   * Enrollment handler.
   *
   * @var PDT_Authenticator_Enrollment|null
   */
  private $enrollment;

  /**
   * Constructor.
   *
   * @param PDT_Authenticator_TOTP             $totp       TOTP service.
   * @param PDT_Authenticator_Enrollment|null $enrollment Enrollment handler.
   */
  public function __construct( $totp, $enrollment = null ) {
    $this->totp = $totp;
    $this->enrollment = $enrollment;
  }

  /**
   * Start TOTP challenge before core email challenge when possible.
   *
   * @param mixed   $pre_auth_result Existing pre-auth result.
   * @param mixed   $user User object.
   * @param string  $username Username.
   * @param string  $password Password.
   * @param object  $two_factor Core 2FA instance.
   * @return mixed
   */
  public function maybe_start_totp_challenge( $pre_auth_result, $user, $username, $password, $two_factor ) {
    if ( null !== $pre_auth_result ) {
      return $pre_auth_result;
    }

    if ( is_wp_error( $user ) || ! ( $user instanceof WP_User ) ) {
      return null;
    }

    global $pdt_settings_page;
    if ( ! $pdt_settings_page || ! method_exists( $pdt_settings_page, 'is_2fa_required_for_user' ) ) {
      return null;
    }

    // ═══════════════════════════════════════════════════════════════
    // SUPER ADMIN EXEMPT CHECK
    // Super Admin (user ID 1 or network super admin) is always exempt
    // This prevents network lockout in worst case scenarios
    // ═══════════════════════════════════════════════════════════════
    if ( $this->is_super_admin_exempt( $user, $pdt_settings_page ) ) {
      return $user;
    }

    if ( ! $pdt_settings_page->is_2fa_required_for_user( $user ) ) {
      return null;
    }

    // Check configured method - only intercept if authenticator is selected
    $configured_method = $pdt_settings_page->get_2fa_method();
    if ( 'authenticator' !== $configured_method ) {
      // Email method selected - let core handle it
      return null;
    }

    if ( ! $this->totp->is_user_enrolled( $user->ID ) ) {
      // Authenticator required but user hasn't completed setup
      // Redirect to enrollment flow instead of showing error
      if ( $this->enrollment ) {
        $token = $this->get_session_token();
        $this->enrollment->start_enrollment_flow( $user, $token );
        // start_enrollment_flow calls exit, but just in case:
        exit;
      }

      // Fallback error if enrollment handler not available
      return new WP_Error(
        'pdt_2fa_authenticator_not_setup',
        sprintf(
          /* translators: %s: Admin settings URL */
          __( 'Two-factor authentication is required but your Authenticator app is not configured. Please log in to the admin panel from a device where you have access, or contact your administrator. If you are an admin, add %s to wp-config.php temporarily to disable 2FA.', 'polanger-admin-menu-manager' ),
          "<code>define('PDT_DISABLE_2FA', true);</code>"
        )
      );
    }

    if ( $this->is_trusted_device( $user->ID ) ) {
      return $user;
    }

    $lockout_remaining = $this->get_lockout_remaining_seconds( $user->ID );
    if ( $lockout_remaining > 0 ) {
      $remaining_minutes = max( 1, (int) ceil( $lockout_remaining / MINUTE_IN_SECONDS ) );
      return new WP_Error(
        'pdt_2fa_locked',
        sprintf(
          /* translators: %d: Remaining lockout time in minutes. */
          __( 'Too many failed verification attempts. Please try again in %d minutes.', 'polanger-admin-menu-manager' ),
          $remaining_minutes
        )
      );
    }

    $token = $this->get_session_token();
    $expiry_minutes = (int) $pdt_settings_page->get_2fa_code_expiry();
    $pending_context = array(
      'user_id'    => $user->ID,
      'remember'   => isset( $_POST['rememberme'] ) && 'forever' === sanitize_text_field( wp_unslash( $_POST['rememberme'] ) ),
      'expires_at' => time() + ( $expiry_minutes * MINUTE_IN_SECONDS ),
      'created_at' => time(),
      'factor'     => 'totp',
      'attempts'   => 0,
    );
    $pending_context = apply_filters( 'pdt_2fa_pending_context', $pending_context, $user, $two_factor );
    set_transient( 'pdt_2fa_pending_' . $token, $pending_context, $expiry_minutes * MINUTE_IN_SECONDS );

    wp_safe_redirect(
      pdt_get_2fa_challenge_url(
        $pending_context,
        array(
          'action' => '2fa_verify',
          'token'  => $token,
          'mode'   => 'totp',
        )
      )
    );
    exit;
  }

  /**
   * Check if user is Super Admin and exempt from 2FA.
   *
   * @param WP_User $user User object.
   * @param object  $settings_page Settings page instance.
   * @return bool
   */
  private function is_super_admin_exempt( $user, $settings_page ) {
    $settings = $settings_page->get_settings();
    
    // If "include super admin" is enabled, no one is exempt
    if ( ! empty( $settings['two_factor_include_super_admin'] ) ) {
      return false;
    }

    // User ID 1 is always exempt (original admin) - prevents total lockout
    if ( 1 === (int) $user->ID ) {
      return true;
    }

    // In multisite, check if user is a super admin
    if ( is_multisite() && is_super_admin( $user->ID ) ) {
      // If this is the ONLY super admin, exempt them
      $super_admins = get_super_admins();
      if ( count( $super_admins ) === 1 ) {
        return true;
      }
    }

    return false;
  }

  /**
   * Handle email fallback request from TOTP challenge screen.
   * Allows users to switch from Authenticator to Email verification.
   *
   * @return void
   */
  public function handle_email_fallback() {
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Route check only.
    $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
    if ( 'pdt_switch_to_email' !== $action ) {
      return;
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Token read only.
    $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
    if ( empty( $token ) ) {
      return;
    }

    // Verify nonce for security
    $nonce = isset( $_GET['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_GET['_wpnonce'] ) ) : '';
    if ( ! wp_verify_nonce( $nonce, 'pdt_switch_email_' . $token ) ) {
      wp_die( esc_html__( 'Security check failed. Please try again.', 'polanger-admin-menu-manager' ) );
    }

    // Get pending context
    $pending = get_transient( 'pdt_2fa_pending_' . $token );
    if ( ! is_array( $pending ) || empty( $pending['user_id'] ) ) {
      wp_safe_redirect( pdt_get_2fa_back_to_login_url() );
      exit;
    }

    $user_id = (int) $pending['user_id'];
    $user = get_user_by( 'id', $user_id );
    if ( ! $user ) {
      wp_safe_redirect( pdt_get_2fa_back_to_login_url( $pending ) );
      exit;
    }

    // Check if email fallback is allowed
    $email_fallback_enabled = apply_filters( 'pdt_2fa_allow_email_fallback', true );
    if ( ! $email_fallback_enabled ) {
      wp_die( esc_html__( 'Email fallback is not enabled.', 'polanger-admin-menu-manager' ) );
    }

    // Get settings for expiry
    global $pdt_settings_page;
    $expiry_minutes = $pdt_settings_page && method_exists( $pdt_settings_page, 'get_2fa_code_expiry' ) 
      ? (int) $pdt_settings_page->get_2fa_code_expiry() 
      : 10;

    // Generate and send email code using core 2FA
    global $pdt_two_factor;
    if ( ! $pdt_two_factor || ! method_exists( $pdt_two_factor, 'send_email_code_for_fallback' ) ) {
      // Fallback: manually generate and send code
      $code = wp_rand( 100000, 999999 );
      $code_hash = wp_hash( (string) $code );
      
      set_transient( 'pdt_2fa_code_' . $user_id, array(
        'code'     => $code_hash,
        'attempts' => 0,
        'created'  => time(),
      ), $expiry_minutes * MINUTE_IN_SECONDS );

      // Send email
      $site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
      $subject = sprintf(
        /* translators: %s: Site name */
        __( '[%s] Your Verification Code', 'polanger-admin-menu-manager' ),
        $site_name
      );
      $message = sprintf(
        /* translators: 1: User display name, 2: Verification code, 3: Expiry minutes, 4: Site name */
        __( "Hello %1\$s,\n\nYour verification code is: %2\$s\n\nThis code will expire in %3\$d minutes.\n\nIf you did not request this code, please ignore this email.\n\nRegards,\n%4\$s", 'polanger-admin-menu-manager' ),
        $user->display_name,
        $code,
        $expiry_minutes,
        $site_name
      );

      $email_sent = wp_mail( $user->user_email, $subject, $message );
      if ( ! $email_sent ) {
        wp_die( esc_html__( 'Failed to send verification email. Please try again.', 'polanger-admin-menu-manager' ) );
      }
    } else {
      $pdt_two_factor->send_email_code_for_fallback( $user );
    }

    // Update pending context to reflect email mode
    $pending['factor'] = 'email_fallback';
    $pending['switched_from'] = 'totp';
    set_transient( 'pdt_2fa_pending_' . $token, $pending, $expiry_minutes * MINUTE_IN_SECONDS );

    // Redirect to email verification screen (remove mode=totp)
    wp_safe_redirect(
      pdt_get_2fa_challenge_url(
        $pending,
        array(
          'action' => '2fa_verify',
          'token'  => $token,
        )
      )
    );
    exit;
  }

  /**
   * Render and handle the TOTP challenge screen.
   *
   * @return void
   */
  public function maybe_render_totp_challenge() {
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Route check only.
    $action = isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '';
    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Route check only.
    $mode = isset( $_GET['mode'] ) ? sanitize_text_field( wp_unslash( $_GET['mode'] ) ) : '';
    if ( '2fa_verify' !== $action || 'totp' !== $mode ) {
      return;
    }

    // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Token read only.
    $token = isset( $_GET['token'] ) ? sanitize_text_field( wp_unslash( $_GET['token'] ) ) : '';
    $pending = get_transient( 'pdt_2fa_pending_' . $token );
    if ( ! is_array( $pending ) || empty( $pending['user_id'] ) || ( isset( $pending['factor'] ) && 'totp' !== $pending['factor'] ) ) {
      wp_safe_redirect( pdt_get_2fa_back_to_login_url() );
      exit;
    }

    $user = get_userdata( (int) $pending['user_id'] );
    if ( ! $user ) {
      delete_transient( 'pdt_2fa_pending_' . $token );
      wp_safe_redirect( pdt_get_2fa_back_to_login_url( $pending ) );
      exit;
    }

    $error_message = '';
    
    // Handle TOTP code verification
    if ( isset( $_POST['pdt_authenticator_verify'] ) ) {
      $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
      if ( ! wp_verify_nonce( $nonce, 'pdt_authenticator_verify' ) ) {
        $error_message = __( 'Security check failed. Please try again.', 'polanger-admin-menu-manager' );
      } else {
        $submitted_code = isset( $_POST['verification_code'] ) ? sanitize_text_field( wp_unslash( $_POST['verification_code'] ) ) : '';
        $result = $this->process_totp_submission( $token, $pending, $submitted_code );
        if ( true === $result ) {
          $trust_device = isset( $_POST['trust_device'] ) && '1' === sanitize_text_field( wp_unslash( $_POST['trust_device'] ) );
          if ( $trust_device ) {
            $this->set_trusted_device( (int) $pending['user_id'] );
          }
          wp_set_auth_cookie( (int) $pending['user_id'], ! empty( $pending['remember'] ) );
          wp_set_current_user( (int) $pending['user_id'] );
          wp_safe_redirect( pdt_get_2fa_success_redirect_url( $pending, $user ) );
          exit;
        }

        if ( is_wp_error( $result ) ) {
          $error_message = $result->get_error_message();
        }
      }
    }
    
    // Handle Recovery Key verification
    if ( isset( $_POST['pdt_authenticator_recovery_verify'] ) ) {
      $nonce = isset( $_POST['_wpnonce'] ) ? sanitize_text_field( wp_unslash( $_POST['_wpnonce'] ) ) : '';
      if ( ! wp_verify_nonce( $nonce, 'pdt_authenticator_recovery' ) ) {
        $error_message = __( 'Security check failed. Please try again.', 'polanger-admin-menu-manager' );
      } else {
        $recovery_code = isset( $_POST['recovery_code'] ) ? sanitize_text_field( wp_unslash( $_POST['recovery_code'] ) ) : '';
        $result = $this->verify_recovery_key_for_totp( (int) $pending['user_id'], $recovery_code, $token );
        if ( true === $result ) {
          wp_set_auth_cookie( (int) $pending['user_id'], ! empty( $pending['remember'] ) );
          wp_set_current_user( (int) $pending['user_id'] );
          wp_safe_redirect( pdt_get_2fa_success_redirect_url( $pending, $user ) );
          exit;
        }

        if ( is_wp_error( $result ) ) {
          $error_message = $result->get_error_message();
        }
      }
    }

    // Enqueue dashicons for icons in the verification screen
    wp_enqueue_style( 'dashicons' );
    
    wp_enqueue_style(
      'pdt-two-factor-verify',
      plugins_url( 'assets/css/two-factor-verify.css', PDT_PLUGIN_FILE ),
      array( 'dashicons' ),
      PDT_VERSION
    );
    ?>
    <!DOCTYPE html>
    <html <?php language_attributes(); ?>>
    <head>
      <meta charset="<?php bloginfo( 'charset' ); ?>">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title><?php esc_html_e( 'Authenticator Verification', 'polanger-admin-menu-manager' ); ?> - <?php bloginfo( 'name' ); ?></title>
      <?php wp_head(); ?>
    </head>
    <body>
      <div class="pdt-2fa-container">
        <div class="pdt-2fa-icon"><span class="dashicons dashicons-shield-alt"></span></div>
        <h1 class="pdt-2fa-title"><?php esc_html_e( 'Authenticator Verification', 'polanger-admin-menu-manager' ); ?></h1>
        <p class="pdt-2fa-description"><?php esc_html_e( 'Open Google Authenticator or Microsoft Authenticator and enter the 6-digit code.', 'polanger-admin-menu-manager' ); ?></p>

        <?php if ( ! empty( $error_message ) ) : ?>
          <div class="pdt-2fa-error"><?php echo esc_html( $error_message ); ?></div>
        <?php endif; ?>

        <form method="post">
          <?php wp_nonce_field( 'pdt_authenticator_verify' ); ?>
          <input type="hidden" name="token" value="<?php echo esc_attr( $token ); ?>" />
          <input type="text" name="verification_code" class="pdt-2fa-input" placeholder="000000" maxlength="6" autocomplete="one-time-code" autofocus required />

          <div class="pdt-2fa-trust">
            <input type="checkbox" name="trust_device" id="trust_device" value="1" />
            <label for="trust_device"><?php esc_html_e( 'Trust this device for 30 days', 'polanger-admin-menu-manager' ); ?></label>
          </div>

          <button type="submit" name="pdt_authenticator_verify" class="pdt-2fa-submit">
            <?php esc_html_e( 'Verify & Login', 'polanger-admin-menu-manager' ); ?>
          </button>
        </form>

        <div class="pdt-2fa-alternatives">
          <p class="pdt-2fa-alt-title"><?php esc_html_e( 'Having trouble?', 'polanger-admin-menu-manager' ); ?></p>
          
          <div class="pdt-2fa-alt-buttons">
            <button type="button" class="pdt-2fa-alt-link" id="pdt-toggle-recovery">
              <span class="dashicons dashicons-backup"></span>
              <?php esc_html_e( 'Use Recovery Key', 'polanger-admin-menu-manager' ); ?>
            </button>
            
            <?php
            // Email fallback option - allows switching to email verification
            $email_fallback_enabled = apply_filters( 'pdt_2fa_allow_email_fallback', true );
            if ( $email_fallback_enabled ) :
            ?>
            <a href="<?php echo esc_url( pdt_get_2fa_challenge_url( $pending, array( 'action' => 'pdt_switch_to_email', 'token' => $token, '_wpnonce' => wp_create_nonce( 'pdt_switch_email_' . $token ) ) ) ); ?>" class="pdt-2fa-alt-link">
              <span class="dashicons dashicons-email-alt"></span>
              <?php esc_html_e( 'Send Code via Email', 'polanger-admin-menu-manager' ); ?>
            </a>
            <?php endif; ?>
          </div>
          
          <div class="pdt-2fa-recovery-form" id="pdt-recovery-form" style="display:none;">
            <p class="pdt-2fa-recovery-desc"><?php esc_html_e( 'Enter one of your recovery keys to access your account.', 'polanger-admin-menu-manager' ); ?></p>
            <form method="post">
              <?php wp_nonce_field( 'pdt_authenticator_recovery' ); ?>
              <input type="hidden" name="token" value="<?php echo esc_attr( $token ); ?>" />
              <input type="hidden" name="is_recovery_key" value="1" />
              <input type="text" name="recovery_code" class="pdt-2fa-input pdt-recovery-input" placeholder="ABCDE12345" maxlength="10" autocomplete="off" />
              <button type="submit" name="pdt_authenticator_recovery_verify" class="pdt-2fa-submit pdt-2fa-recovery-submit">
                <?php esc_html_e( 'Verify Recovery Key', 'polanger-admin-menu-manager' ); ?>
              </button>
            </form>
            <button type="button" class="pdt-2fa-alt-link" id="pdt-back-to-totp">
              <span class="dashicons dashicons-smartphone"></span>
              <?php esc_html_e( 'Back to Authenticator', 'polanger-admin-menu-manager' ); ?>
            </button>
          </div>
        </div>

        <div class="pdt-2fa-back">
          <a href="<?php echo esc_url( pdt_get_2fa_back_to_login_url( $pending ) ); ?>">&larr; <?php esc_html_e( 'Back to login', 'polanger-admin-menu-manager' ); ?></a>
        </div>
      </div>
      <script>
      (function() {
        var toggleBtn = document.getElementById('pdt-toggle-recovery');
        var backBtn = document.getElementById('pdt-back-to-totp');
        var recoveryForm = document.getElementById('pdt-recovery-form');
        var mainForm = document.querySelector('.pdt-2fa-container > form');
        
        if (toggleBtn && recoveryForm && mainForm) {
          toggleBtn.addEventListener('click', function() {
            mainForm.style.display = 'none';
            toggleBtn.style.display = 'none';
            recoveryForm.style.display = 'block';
            var recoveryInput = recoveryForm.querySelector('input[name="recovery_code"]');
            if (recoveryInput) recoveryInput.focus();
          });
        }
        
        if (backBtn && recoveryForm && mainForm && toggleBtn) {
          backBtn.addEventListener('click', function() {
            recoveryForm.style.display = 'none';
            mainForm.style.display = 'block';
            toggleBtn.style.display = 'inline-flex';
            mainForm.querySelector('input[name="verification_code"]').focus();
          });
        }
      })();
      </script>
      <?php wp_footer(); ?>
    </body>
    </html>
    <?php
    exit;
  }

  /**
   * Allow TOTP verification in core provider filter.
   *
   * @param mixed $provider_result Existing provider result.
   * @param mixed $pending Pending challenge context.
   * @param string $submitted_code Submitted code.
   * @param object $two_factor Core 2FA instance.
   * @return mixed
   */
  public function verify_submitted_totp( $provider_result, $pending, $submitted_code, $two_factor ) {
    if ( null !== $provider_result ) {
      return $provider_result;
    }

    $token = isset( $_POST['token'] ) ? sanitize_text_field( wp_unslash( $_POST['token'] ) ) : '';
    if ( '' === $token ) {
      return null;
    }

    $pending_context = get_transient( 'pdt_2fa_pending_' . $token );
    if ( ! is_array( $pending_context ) || empty( $pending_context['user_id'] ) ) {
      return null;
    }

    $is_totp_mode = isset( $_GET['mode'] ) && 'totp' === sanitize_text_field( wp_unslash( $_GET['mode'] ) );
    if ( ! $is_totp_mode ) {
      return null;
    }

    return $this->totp->verify_code( (int) $pending_context['user_id'], $submitted_code );
  }

  /**
   * Validate and process submitted TOTP code.
   *
   * @param string $token Pending token.
   * @param array $pending Pending context.
   * @param string $submitted_code Submitted code.
   * @return bool|WP_Error
   */
  private function process_totp_submission( $token, $pending, $submitted_code ) {
    global $pdt_settings_page;
    $max_attempts = $pdt_settings_page && method_exists( $pdt_settings_page, 'get_2fa_max_attempts' ) ? (int) $pdt_settings_page->get_2fa_max_attempts() : 3;

    if ( ! $this->totp->verify_code( (int) $pending['user_id'], $submitted_code ) ) {
      $pending['attempts'] = isset( $pending['attempts'] ) ? absint( $pending['attempts'] ) + 1 : 1;
      set_transient( 'pdt_2fa_pending_' . $token, $pending, MINUTE_IN_SECONDS * 10 );

      if ( $pending['attempts'] >= $max_attempts ) {
        delete_transient( 'pdt_2fa_pending_' . $token );
        $this->set_2fa_lockout( (int) $pending['user_id'] );
        return new WP_Error( 'too_many_attempts', $this->get_lockout_message( (int) $pending['user_id'] ) );
      }

      $remaining = $max_attempts - $pending['attempts'];
      return new WP_Error(
        'invalid_code',
        sprintf(
          /* translators: %d: Remaining attempts. */
          __( 'Invalid authenticator code. %d attempts remaining.', 'polanger-admin-menu-manager' ),
          $remaining
        )
      );
    }

    delete_transient( 'pdt_2fa_pending_' . $token );
    delete_transient( 'pdt_2fa_code_' . (int) $pending['user_id'] );
    return true;
  }

  /**
   * Set lockout state for user.
   *
   * @param int $user_id User ID.
   * @return void
   */
  private function set_2fa_lockout( $user_id ) {
    global $pdt_settings_page;
    $minutes = $pdt_settings_page && method_exists( $pdt_settings_page, 'get_2fa_lockout_minutes' ) ? (int) $pdt_settings_page->get_2fa_lockout_minutes() : 30;
    set_transient( 'pdt_2fa_lockout_' . $user_id, time(), $minutes * MINUTE_IN_SECONDS );
  }

  /**
   * Get lockout message.
   *
   * @param int $user_id User ID.
   * @return string
   */
  private function get_lockout_message( $user_id ) {
    $remaining_seconds = $this->get_lockout_remaining_seconds( $user_id );
    $remaining_minutes = max( 1, (int) ceil( $remaining_seconds / MINUTE_IN_SECONDS ) );
    return sprintf(
      /* translators: %d: Remaining lockout time in minutes. */
      __( 'Too many failed attempts. Login is temporarily locked. Please try again in %d minutes.', 'polanger-admin-menu-manager' ),
      $remaining_minutes
    );
  }

  /**
   * Get lockout remaining seconds.
   *
   * @param int $user_id User ID.
   * @return int
   */
  private function get_lockout_remaining_seconds( $user_id ) {
    global $pdt_settings_page;
    $lockout_started = get_transient( 'pdt_2fa_lockout_' . $user_id );
    if ( false === $lockout_started ) {
      return 0;
    }

    $minutes = $pdt_settings_page && method_exists( $pdt_settings_page, 'get_2fa_lockout_minutes' ) ? (int) $pdt_settings_page->get_2fa_lockout_minutes() : 30;
    $lockout_seconds = $minutes * MINUTE_IN_SECONDS;
    $elapsed = time() - absint( $lockout_started );

    return max( 0, $lockout_seconds - $elapsed );
  }

  /**
   * Check trusted device state.
   *
   * @param int $user_id User ID.
   * @return bool
   */
  private function is_trusted_device( $user_id ) {
    $cookie_name = 'pdt_trusted_device_' . $user_id;
    if ( ! isset( $_COOKIE[ $cookie_name ] ) ) {
      return false;
    }

    $cookie_value = sanitize_text_field( wp_unslash( $_COOKIE[ $cookie_name ] ) );
    $stored_hash = get_user_meta( $user_id, 'pdt_trusted_device_hash', true );
    if ( empty( $stored_hash ) || ! hash_equals( (string) $stored_hash, (string) $cookie_value ) ) {
      return false;
    }

    $device_hash = $this->generate_device_hash( $user_id );
    $expected = hash_hmac( 'sha256', $device_hash, wp_salt( 'auth' ) );
    return hash_equals( $expected, $cookie_value );
  }

  /**
   * Store trusted device state.
   *
   * @param int $user_id User ID.
   * @return void
   */
  private function set_trusted_device( $user_id ) {
    $device_hash = $this->generate_device_hash( $user_id );
    $cookie_value = hash_hmac( 'sha256', $device_hash, wp_salt( 'auth' ) );
    $expiry = time() + ( 30 * DAY_IN_SECONDS );

    update_user_meta( $user_id, 'pdt_trusted_device_hash', $cookie_value );
    update_user_meta( $user_id, 'pdt_trusted_device_created', time() );
    setcookie( 'pdt_trusted_device_' . $user_id, $cookie_value, $expiry, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
  }

  /**
   * Create user device hash.
   *
   * @param int $user_id User ID.
   * @return string
   */
  private function generate_device_hash( $user_id ) {
    $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
    return hash( 'sha256', $user_id . $user_agent . wp_salt( 'auth' ) );
  }

  /**
   * Get or create session token used by core 2FA flow.
   *
   * @return string
   */
  private function get_session_token() {
    if ( ! isset( $_COOKIE['pdt_2fa_session'] ) ) {
      $token = wp_generate_password( 32, false );
      setcookie( 'pdt_2fa_session', $token, time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
      return $token;
    }

    return sanitize_text_field( wp_unslash( $_COOKIE['pdt_2fa_session'] ) );
  }

  /**
   * Verify recovery key for TOTP challenge.
   * Uses core 2FA recovery key system.
   *
   * @param int    $user_id User ID.
   * @param string $submitted_key Submitted recovery key.
   * @param string $token Session token.
   * @return bool|WP_Error
   */
  private function verify_recovery_key_for_totp( $user_id, $submitted_key, $token ) {
    // Recovery keys are 10 alphanumeric characters
    $submitted_key = strtoupper( preg_replace( '/[^a-zA-Z0-9]/', '', $submitted_key ) );
    if ( strlen( $submitted_key ) !== 10 ) {
      return new WP_Error(
        'invalid_recovery_key',
        __( 'Invalid recovery key format. Recovery keys are 10 characters.', 'polanger-admin-menu-manager' )
      );
    }

    // Get stored recovery keys
    $stored_keys = get_user_meta( $user_id, 'pdt_2fa_recovery_keys', true );
    if ( empty( $stored_keys ) || ! is_array( $stored_keys ) ) {
      return new WP_Error(
        'no_recovery_keys',
        __( 'No recovery keys found. Please contact administrator.', 'polanger-admin-menu-manager' )
      );
    }

    // Check if submitted key matches any stored key
    $key_found = false;
    $key_index = -1;
    foreach ( $stored_keys as $index => $stored_hash ) {
      if ( hash_equals( (string) $stored_hash, (string) wp_hash( $submitted_key ) ) ) {
        $key_found = true;
        $key_index = $index;
        break;
      }
    }

    if ( ! $key_found ) {
      // Log failed attempt
      global $pdt_two_factor;
      if ( $pdt_two_factor && method_exists( $pdt_two_factor, 'log_security_event' ) ) {
        $pdt_two_factor->log_security_event( '2fa_recovery_key_failed', 'warning', array(
          'user_id' => $user_id,
          'reason'  => 'Invalid recovery key submitted during TOTP challenge',
        ) );
      }

      return new WP_Error(
        'invalid_recovery_key',
        __( 'Invalid recovery key. Please check and try again.', 'polanger-admin-menu-manager' )
      );
    }

    // Remove used key (one-time use)
    unset( $stored_keys[ $key_index ] );
    $stored_keys = array_values( $stored_keys );
    update_user_meta( $user_id, 'pdt_2fa_recovery_keys', $stored_keys );

    // Log successful recovery key usage
    global $pdt_two_factor;
    if ( $pdt_two_factor && method_exists( $pdt_two_factor, 'log_security_event' ) ) {
      $pdt_two_factor->log_security_event( '2fa_recovery_key_used', 'warning', array(
        'user_id'        => $user_id,
        'reason'         => 'User logged in using recovery key (TOTP challenge)',
        'remaining_keys' => count( $stored_keys ),
      ) );
    }

    // Clean up pending transient
    delete_transient( 'pdt_2fa_pending_' . $token );

    return true;
  }
}
