<?php

namespace WPSecurityNinja\Plugin;

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

use Da\TwoFA\Manager;
use Da\TwoFA\Service\TOTPSecretKeyUriGeneratorService;
use WPSecurityNinja\Plugin\chillerlan\QRCode\QRCode;
use WPSecurityNinja\Plugin\chillerlan\QRCode\QROptions;
use WP_User;

class Wf_Sn_2fa {

	private static $instance = null;
	private static $options  = null;
	private static $manager  = null;

	/**
	 * Stores password-based authentication sessions to be invalidated before 2FA.
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private static $password_auth_tokens = array();

	/**
	 * Rate limiting for temp_token attempts
	 *
	 * @since 1.0.0
	 * @var array
	 */
	private static $rate_limit_cache = array();

	public static function init() {
		if ( null === self::$instance ) {
			self::$instance = new self();
		}
		return self::$instance;
	}

	private function __construct() {
		// Defer options loading until init hook
		add_action( 'init', array( $this, 'load_options' ) );

		// Defer Manager initialization until needed
		add_action( 'wp_ajax_secnin_generate_qr_code', array( $this, 'ajax_generate_qr_code' ) );
		add_action( 'wp_ajax_nopriv_secnin_generate_qr_code', array( $this, 'ajax_generate_qr_code' ) );
		add_action( 'wp_ajax_secnin_skip_2fa', array( $this, 'ajax_skip_2fa' ) );
		add_action( 'wp_ajax_nopriv_secnin_skip_2fa', array( $this, 'ajax_skip_2fa' ) );
		add_action( 'wp_ajax_secnin_verify_2fa_code', array( $this, 'ajax_verify_2fa_code' ) );
		add_action( 'wp_ajax_nopriv_secnin_verify_2fa_code', array( $this, 'ajax_verify_2fa_code' ) );
		add_action( 'wp_ajax_secnin_send_2fa_email', array( $this, 'ajax_send_2fa_email' ) );
		add_action( 'wp_ajax_nopriv_secnin_send_2fa_email', array( $this, 'ajax_send_2fa_email' ) );
		
		// Admin 2FA setup handlers
		add_action( 'wp_ajax_secnin_admin_generate_qr', array( $this, 'ajax_admin_generate_qr' ) );
		add_action( 'wp_ajax_secnin_admin_verify_2fa', array( $this, 'ajax_admin_verify_2fa' ) );
		add_action( 'wp_ajax_secnin_admin_reset_2fa', array( $this, 'ajax_admin_reset_2fa' ) );
	}

	/**
	 * Load options during init hook to ensure translations are available
	 *
	 * @author  Unknown
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, April 3rd, 2025.
	 * @access  public
	 * @return  void
	 */
	public function load_options() {
		if ( is_null( self::$options ) ) {
			self::$options = self::get_options();

			if ( ! empty( self::$options['2fa_enabled'] ) ) {
				$this->register_hooks();
			}
		}
	}

	private function init_manager() {
		if ( is_null( self::$manager ) ) {
			try {

				$class_name = 'Da\TwoFA\Manager';
				if ( ! class_exists( $class_name ) ) {
					// Check if vendor directory exists
					if ( ! file_exists( WF_SN_PLUGIN_DIR . 'vendor/2amigos/2fa-library/src/Manager.php' ) ) {
						throw new \Exception( '2FA library files not found. Path: ' . WF_SN_PLUGIN_DIR . 'vendor/2amigos/2fa-library/src/Manager.php' );
					}

					// Try direct include if autoloader fails
					require_once WF_SN_PLUGIN_DIR . 'vendor/2amigos/2fa-library/src/Manager.php';

					if ( ! class_exists( $class_name ) ) {
						// Let's check if it's in the prefixed namespace
						$prefixed_class = 'WPSecurityNinja\Plugin\Da\TwoFA\Manager';
						if ( class_exists( $prefixed_class ) ) {
							$class_name = $prefixed_class;
						} else {
							throw new \Exception( '2FA Manager class not found in either namespace. Check composer installation.' );
						}
					}
				}

				self::$manager = new $class_name();

			} catch ( \Exception $e ) {
				throw $e;
			}
		}
		return self::$manager;
	}

	/**
	 * register_hooks.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, June 4th, 2024.
	 * @access  private
	 * @return  void
	 */
	private function register_hooks() {
		// Authentication hooks
		add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );
		add_action( 'clear_auth_cookie', array( $this, 'clear_2fa_session' ) );

		add_action( 'edit_user_profile', array( $this, 'add_bypass_2fa_checkbox' ) );
		add_action( 'show_user_profile', array( $this, 'add_bypass_2fa_checkbox' ) );
		add_action( 'edit_user_profile_update', array( $this, 'save_bypass_2fa_checkbox' ) );
		add_action( 'personal_options_update', array( $this, 'save_bypass_2fa_checkbox' ) );
		
		// Enqueue admin 2FA scripts
		add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin_2fa_scripts' ) );

		// Add hooks for collecting auth cookies
		add_action( 'set_auth_cookie', array( __CLASS__, 'collect_auth_cookie_tokens' ) );
		add_action( 'set_logged_in_cookie', array( __CLASS__, 'collect_auth_cookie_tokens' ) );
	}

	/**
	 * Handles the login process for users with 2FA enabled.
	 *
	 * @param string $user_login The user's login name.
	 * @param WP_User $user The user object.
	 * @return void
	 */
	public static function wp_login( $user_login, $user ) {
		if ( ! $user instanceof WP_User || ! isset( $user->ID ) ) {
			return;
		}
		$is_user_using_two_factor   = self::is_user_using_two_factor( $user->ID );
		$should_user_use_two_factor = self::should_user_use_two_factor( $user->ID );

		$my_options = self::get_options();

		if ( isset( $my_options['2fa_enabled'] ) && $my_options['2fa_enabled'] ) {
			if ( $should_user_use_two_factor ) {
				if ( isset( $_GET['skip_2fa'] ) && '1' === $_GET['skip_2fa'] &&
					isset( $_GET['nonce'] ) && wp_verify_nonce( sanitize_key( $_GET['nonce'] ), 'skip_2fa_nonce' ) ) {

					$enabled_timestamp = isset( $my_options['2fa_enabled_timestamp'] ) ? intval( $my_options['2fa_enabled_timestamp'] ) : 0;
					$grace_period      = isset( $my_options['2fa_grace_period'] ) ? intval( $my_options['2fa_grace_period'] ) : 0;
					$current_time      = time();
					$time_left         = ( $enabled_timestamp + ( $grace_period * DAY_IN_SECONDS ) ) - $current_time;

					if ( $time_left > 0 ) {
						self::handle_2fa_session( $user, true );

						wp_set_current_user( $user->ID, $user->user_login );
						wp_set_auth_cookie( $user->ID, true );

						remove_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10 );
						do_action( 'wp_login', $user->user_login, $user );
						add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );

						// Capture redirect_to from request (same as main 2FA flow) so POST login redirect is preserved.
						$skip_redirect_to = '';
						if ( isset( $_REQUEST['redirect_to'] ) ) {
							$skip_redirect_to = esc_url_raw( $_REQUEST['redirect_to'] );
						} elseif ( isset( $_GET['redirect_to'] ) ) {
							$skip_redirect_to = esc_url_raw( $_GET['redirect_to'] );
						} elseif ( isset( $_POST['redirect_to'] ) ) {
							$skip_redirect_to = esc_url_raw( $_POST['redirect_to'] );
						}
						$redirect_to = self::get_redirect_url( $user, $skip_redirect_to );
						wp_safe_redirect( $redirect_to );
						exit;
					}
				}

				self::handle_2fa_session( $user );

				// Capture redirect_to from request before creating token
				$redirect_to = '';
				if ( isset( $_REQUEST['redirect_to'] ) ) {
					$redirect_to = esc_url_raw( $_REQUEST['redirect_to'] );
				} elseif ( isset( $_GET['redirect_to'] ) ) {
					$redirect_to = esc_url_raw( $_GET['redirect_to'] );
				} elseif ( isset( $_POST['redirect_to'] ) ) {
					$redirect_to = esc_url_raw( $_POST['redirect_to'] );
				}

				if ( ! $is_user_using_two_factor ) {
					$temp_token = self::create_secure_temp_token( $user->ID, $redirect_to );
					self::render_2fa_verify_page( $user, $temp_token );
				} else {
					$temp_token = self::create_secure_temp_token( $user->ID, $redirect_to );
					self::render_2fa_verify_page( $user, $temp_token );
				}
				exit;
			}
		}
	}

	/**
	 * Generate QR code for 2FA setup
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, July 20th, 2023.
	 * @access  public static
	 * @return  void
	 */
	public function ajax_generate_qr_code() {
		try {
			if ( empty( $_POST['temp_token'] ) ) {
				throw new \Exception( __( 'Missing temp_token', 'security-ninja' ) );
			}

			$temp_token   = sanitize_text_field( $_POST['temp_token'] );
			$session_data = self::validate_temp_token( $temp_token );

			if ( ! $session_data ) {
				throw new \Exception( __( 'Invalid or expired session', 'security-ninja' ) );
			}

			$user_id = $session_data['user_id'];

			// Increment attempt counter for rate limiting
			self::increment_token_attempts( $temp_token, $session_data );

			$user = get_user_by( 'ID', $user_id );
			if ( ! $user ) {
				throw new \Exception( 'User not found' );
			}

			// Initialize manager using the shared method
			$manager = $this->init_manager();

			// Generate new secret
			$secret = $manager->generateSecretKey();

			// Store temporary secret
			if ( ! set_transient( 'secnin_2fa_temp_secret_' . $temp_token, $secret, 30 * MINUTE_IN_SECONDS ) ) {
				throw new \Exception( 'Failed to store secret' );
			}

			// Get site info for the label
		$site_url  = Wf_Sn_Vu::remove_http( site_url() );

		// Create the otpauth URI manually to ensure correct format
		// Format: site_url:username (e.g., "example.com:john")
		$label   = rawurlencode( $site_url );
		$issuer  = rawurlencode( $site_url );
		$account = rawurlencode( $user->user_login );

			// Format: otpauth://totp/Label:account?secret=SECRET&issuer=Issuer
			$uri = sprintf(
				'otpauth://totp/%s:%s?secret=%s&issuer=%s',
				$label,
				$account,
				$secret,
				$issuer
			);

			// Generate QR code
			$options = new QROptions(
				array(
					'outputType'  => QRCode::OUTPUT_MARKUP_SVG,
					'imageBase64' => true,
				)
			);

			$qrcode           = new QRCode( $options );
			$qr_code_data_uri = $qrcode->render( $uri );

			wp_send_json_success(
				array(
					'qr_code' => $qr_code_data_uri,
					'secret'  => $secret,
					'message' => __( 'QR code generated successfully', 'security-ninja' ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * Adds 2FA setup section to user profile page.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.1  Thursday, July 18th, 2024.
	 * @access  public static
	 * @param   WP_User $user The user object.
	 * @return  void
	 */
	public static function add_bypass_2fa_checkbox( $user ) {
		if ( ! $user instanceof WP_User ) {
			return;
		}

		$bypass2fa = get_user_meta( $user->ID, 'bypass_2fa', true );
		$is_2fa_setup = self::is_2fa_setup_complete( $user->ID );
		$should_use_2fa = self::should_user_use_two_factor( $user->ID );
		$is_admin = in_array( 'administrator', (array) $user->roles, true );
		
		?>
		<h3><?php esc_html_e( 'Two Factor Authentication', 'security-ninja' ); ?></h3>
		<table class="form-table">
			<?php if ( $is_admin ) : ?>
			<tr>
				<th><label for="bypass_2fa"><?php esc_html_e( 'Bypass 2FA', 'security-ninja' ); ?></label></th>
				<td>
					<input type="checkbox" name="bypass_2fa" id="bypass_2fa" value="1" <?php checked( $bypass2fa, '1' ); ?> />
					<span class="description">
						<?php esc_html_e( 'Check this box to allow the user to bypass Two Factor Authentication.', 'security-ninja' ); ?>
					</span>
				</td>
			</tr>
			<?php endif; ?>
			
			<?php if ( $should_use_2fa || $is_admin ) : ?>
			<tr>
				<th><label><?php esc_html_e( '2FA Status', 'security-ninja' ); ?></label></th>
				<td>
					<?php if ( $is_2fa_setup ) : ?>
						<span class="dashicons dashicons-yes-alt" style="color: #46b450;"></span>
						<strong style="color: #46b450;"><?php esc_html_e( '2FA is configured', 'security-ninja' ); ?></strong>
						<p class="description">
							<?php esc_html_e( 'This user has completed 2FA setup.', 'security-ninja' ); ?>
							<?php if ( $is_admin ) : ?>
								<button type="button" id="reset-2fa" class="button button-secondary" style="margin-left: 10px;">
									<?php esc_html_e( 'Reset 2FA', 'security-ninja' ); ?>
								</button>
							<?php endif; ?>
						</p>
					<?php else : ?>
						<span class="dashicons dashicons-warning" style="color: #dc3232;"></span>
						<strong style="color: #dc3232;"><?php esc_html_e( '2FA not configured', 'security-ninja' ); ?></strong>
						<p class="description">
							<?php if ( $should_use_2fa ) : ?>
								<?php esc_html_e( 'This user is required to set up 2FA but has not done so yet.', 'security-ninja' ); ?>
							<?php else : ?>
								<?php esc_html_e( 'This user has not set up 2FA yet.', 'security-ninja' ); ?>
							<?php endif; ?>
						</p>
					<?php endif; ?>
				</td>
			</tr>
			
			<?php if ( ! $is_2fa_setup ) : ?>
			<tr>
				<th><label><?php esc_html_e( 'Setup 2FA', 'security-ninja' ); ?></label></th>
				<td>
					<div id="admin-2fa-setup">
						<?php if ( $is_admin ) : ?>
							<p class="description">
								<?php esc_html_e( 'As an administrator, you can set up 2FA for this user. The user will need to scan the QR code with their authenticator app and enter the verification code.', 'security-ninja' ); ?>
							</p>
						<?php endif; ?>
						<button type="button" id="setup-2fa" class="button button-primary">
							<?php esc_html_e( 'Setup 2FA for this user', 'security-ninja' ); ?>
						</button>
						<div id="admin-2fa-qr-container" style="display: none; margin-top: 20px;">
							<div class="qr-code-admin">
								<img id="admin-qr-code-img" 
									src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Crect width='200' height='200' fill='%23ffffff' stroke='%23eee' stroke-width='2'/%3E%3C/svg%3E" 
									alt="<?php esc_attr_e( 'QR Code Loading', 'security-ninja' ); ?>" 
									style="display: inline-block; width: 200px; height: 200px;" />


									<div id="admin-secret-container" style="display: none; margin-top: 15px; padding: 15px; background: #f0f0f1; border-radius: 5px; border: 1px solid #ddd;">
								<p style="margin: 0 0 10px 0; font-weight: bold;">
									<?php esc_html_e( 'Manual Entry Secret Key:', 'security-ninja' ); ?>
								</p>
								<div style="display: flex; align-items: center; gap: 10px;">
									<input type="text" id="admin-secret-key" readonly style="text-align: center; flex: 1; font-family: monospace; font-size: 14px; padding: 8px; border: 1px solid #ccc; border-radius: 3px; background: #fff;" />
									<button type="button" id="admin-copy-secret" class="button button-secondary">
										<?php esc_html_e( 'Copy', 'security-ninja' ); ?>
									</button>
								</div>
								<p style="margin: 10px 0 0 0; font-size: 12px; color: #666;">
									<?php esc_html_e( 'Use this secret key to manually add the account to your authenticator app if you cannot scan the QR code.', 'security-ninja' ); ?>
								</p>
							</div>
								<div id="admin-qr-code-err"></div>
								<div class="spinner" id="admin-qr-spinner"></div>
								<button id="admin-generate-qr" class="button button-secondary" style="display: none;">
									<?php esc_html_e( 'Generate New QR Code', 'security-ninja' ); ?>
								</button>
							</div>
							
							<div id="admin-2fa-verification" style="margin-top: 20px;">
								<p>
									<label for="admin-2fa-code">
										<?php esc_html_e( 'Enter the 6-digit code from your authentication app:', 'security-ninja' ); ?>
									</label>
									<input type="text" name="admin-2fa-code" id="admin-2fa-code" class="regular-text" value="" size="6" maxlength="6" pattern="\d{6}" inputmode="numeric" autocapitalize="off" autocomplete="off" />
								</p>
								<button type="button" id="admin-verify-2fa" class="button button-primary" disabled>
									<?php esc_html_e( 'Verify & Save', 'security-ninja' ); ?>
								</button>
								<div id="admin-2fa-verify-msg"></div>
							</div>
						</div>
					</div>
				</td>
			</tr>
			<?php endif; ?>
			<?php endif; ?>
		</table>
		
		<style>
		.qr-code-admin {
			text-align: center;
			margin: 20px 0;
			padding: 20px;
			background: #f9f9f9;
			border-radius: 5px;
			border: 1px solid #ddd;
		}
		
		.qr-code-admin img {
			display: inline-block;
			max-width: 100%;
			height: auto;
			border: 1px solid #ccc;
			border-radius: 3px;
		}
		
		.spinner {
			background: url(<?php echo esc_url( admin_url( 'images/spinner.gif' ) ); ?>) no-repeat;
			background-size: 20px 20px;
			display: none;
			float: right;
			opacity: 0.7;
			width: 20px;
			height: 20px;
			margin: 5px 5px 0;
		}
		
		.spinner.is-active {
			display: inline-block;
		}
		
		#admin-2fa-verify-msg {
			margin-top: 10px;
		}
		
		#admin-2fa-verify-msg .notice {
			margin: 5px 0;
			padding: 8px 12px;
		}
		
		#admin-2fa-code {
			width: 120px;
			text-align: center;
			font-size: 16px;
			letter-spacing: 2px;
		}
		</style>
		
		<script>
		var admin_2fa_config = {
			ajaxurl: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
			user_id: <?php echo intval( $user->ID ); ?>,
			nonce: '<?php echo esc_js( wp_create_nonce( 'admin_2fa_setup_' . $user->ID ) ); ?>',
			messages: {
				verifying: '<?php echo esc_js( __( 'Verifying code...', 'security-ninja' ) ); ?>',
				network_error: '<?php echo esc_js( __( 'Network error. Please check your connection and try again.', 'security-ninja' ) ); ?>',
				qr_error: '<?php echo esc_js( __( 'Error generating QR code. Please try again.', 'security-ninja' ) ); ?>',
				success: '<?php echo esc_js( __( '2FA setup completed successfully!', 'security-ninja' ) ); ?>',
				invalid_code: '<?php echo esc_js( __( 'Invalid code. Please try again.', 'security-ninja' ) ); ?>',
				unknown_error: '<?php echo esc_js( __( 'An unknown error occurred. Please try again.', 'security-ninja' ) ); ?>',
				reset_confirm: '<?php echo esc_js( __( 'Are you sure you want to reset 2FA for this user? This action cannot be undone.', 'security-ninja' ) ); ?>'
			}
		};
		</script>
		<?php
	}

	/**
	 * save_bypass_2fa_checkbox.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Thursday, June 6th, 2024.
	 * @access  public static
	 * @global
	 * @param   mixed   $user_id
	 * @return  void
	 */
	public static function save_bypass_2fa_checkbox( $user_id ) {
		if ( ! current_user_can( 'edit_user', $user_id ) ) {
			return false;
		}

		$bypass_2fa = isset( $_POST['bypass_2fa'] ) ? sanitize_text_field( $_POST['bypass_2fa'] ) : '';
		update_user_meta( $user_id, 'bypass_2fa', $bypass_2fa );
	}

	/**
	 * Enqueue admin 2FA scripts on user profile pages
	 *
	 * @param string $hook_suffix Current admin page hook suffix
	 */
	public function enqueue_admin_2fa_scripts( $hook_suffix ) {
		// Only enqueue on user profile pages
		if ( 'profile.php' === $hook_suffix || 'user-edit.php' === $hook_suffix ) {
			wp_enqueue_script(
				'admin-two-factor-auth',
				plugins_url( '/js/admin-two-factor-auth.js', __FILE__ ),
				array( 'jquery' ),
				'1.0.0',
				true
			);
		}
	}

	/**
	 * Checks if the 2FA code is validated for the current session.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.0  Tuesday, June 4th, 2024.
	 * @access  public static
	 * @param   WP_User $user The user object to check.
	 * @return  bool    True if the code is validated, false otherwise.
	 */
	public static function is_code_validated_for_session( $user ) {
		if ( ! $user instanceof WP_User ) {
			return false;
		}

		$code_validated = get_user_meta( $user->ID, 'secnin_2fa_code_validated', true );
		return ! empty( $code_validated );
	}

	/**
	 * Clears user session for 2FA when logging out.
	 *
	 * This method is called when the auth cookie is cleared, typically during logout.
	 * It removes the 2FA session validation metadata for the current user.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.1  Tuesday, June 4th, 2024.
	 * @access  public static
	 * @return  void
	 */
	public static function clear_2fa_session() {
		$user_id = get_current_user_id();
		if ( $user_id > 0 ) {
			delete_user_meta( $user_id, 'secnin_2fa_session_validated' );
		}
	}

	/**
	 * Retrieves the 2FA options.
	 *
	 * This method fetches all options and filters out only those related to 2FA.
	 * If options have already been retrieved, it returns the cached version.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.1  Tuesday, June 4th, 2024.
	 * @access  public static
	 * @return  array An array of 2FA-related options.
	 */
	public static function get_options() {
		if ( ! is_null( self::$options ) ) {
			return self::$options;
		}

		$options = \WPSecurityNinja\Plugin\wf_sn_cf::get_options();

		$options = array_filter(
			$options,
			function ( $key ) {
				return strpos( $key, '2fa_' ) === 0;
			},
			ARRAY_FILTER_USE_KEY
		);

		self::$options = $options;
		return self::$options;
	}

	/**
	 * Retrieve or generate a passphrase for encryption.
	 *
	 * This method retrieves an existing passphrase from the WordPress options table,
	 * or generates a new one if it doesn't exist.
	 *
	 * @since   v0.0.1
	 * @version v1.0.0  Wednesday, July 17th, 2024.
	 * @access  private
	 * @return  string The retrieved or newly generated passphrase.
	 * @throws  \Exception If a secure random bytes generation fails.
	 */
	private static function get_passphrase() {
		$option_name = 'secnin_2fa_passphrase';
		$passphrase  = get_option( $option_name );

		if ( empty( $passphrase ) ) {
			try {
				// Generate a secure 256-bit passphrase.
				$random_bytes = random_bytes( 32 );
				$passphrase   = bin2hex( $random_bytes );

				$updated = update_option( $option_name, $passphrase );
				if ( ! $updated ) {
					throw new \Exception( __( 'Failed to save the generated passphrase.', 'security-ninja' ) );
				}
			} catch ( \Exception $e ) {
				// Log the error or handle it appropriately
				// Log the error using the event logger
				wf_sn_el_modules::log_event(
					'security_ninja',
					'2fa_passphrase_generation_failed',
					sprintf(
						/* translators: %s: Error message */
						__( 'Failed to generate or save 2FA passphrase: %s', 'security-ninja' ),
						$e->getMessage()
					)
				);
				throw $e; // Re-throw the exception for the caller to handle
			}
		}

		return $passphrase;
	}

	/**
	 * Encrypt a secret with IV concatenation.
	 *
	 * @param string $secret The secret to encrypt.
	 * @param string $passphrase The passphrase to use for encryption.
	 * @return string|false The concatenated IV and encrypted secret, or false on failure.
	 */
	private static function encrypt_secret( $secret, $passphrase ) {
		if ( empty( $secret ) || empty( $passphrase ) ) {
			return false;
		}

		$iv               = openssl_random_pseudo_bytes( 16 );
		$encrypted_secret = openssl_encrypt(
			$secret,
			'aes-256-cbc',
			$passphrase,
			OPENSSL_RAW_DATA,
			$iv
		);

		if ( false === $encrypted_secret ) {
			return false;
		}

		return base64_encode( $iv . $encrypted_secret );
	}

	/**
	 * Decrypt a secret with IV extraction.
	 *
	 * @param string $data The concatenated IV and encrypted secret.
	 * @param string $passphrase The passphrase to use for decryption.
	 * @return string|false The decrypted secret, or false on failure.
	 */
	private static function decrypt_secret( $data, $passphrase ) {
		if ( empty( $data ) || empty( $passphrase ) ) {
			return false;
		}

		$decoded_data = base64_decode( $data );
		if ( false === $decoded_data ) {
			return false;
		}

		if ( strlen( $decoded_data ) < 16 ) {
			return false;
		}

		$iv               = substr( $decoded_data, 0, 16 );
		$encrypted_secret = substr( $decoded_data, 16 );

		$decrypted = openssl_decrypt(
			$encrypted_secret,
			'aes-256-cbc',
			$passphrase,
			OPENSSL_RAW_DATA,
			$iv
		);

		if ( false !== $decrypted ) {
			return $decrypted;
		} else {
			return false;
		}
	}

	/**
	 * Verify the 2FA code submitted by the user.
	 *
	 * This method handles the AJAX request for verifying the 2FA code.
	 * It checks the nonce, validates the code, and logs the user in if successful.
	 *
	 * @since   v0.0.1
	 * @version v1.0.2  Friday, July 19th, 2024.
	 * @access  public static
	 * @return  void
	 */
	public function ajax_verify_2fa_code() {
		try {
			if ( empty( $_POST['code'] ) || empty( $_POST['temp_token'] ) ) {
				throw new \Exception( __( 'Missing required information.', 'security-ninja' ) );
			}

			// Initialize manager using the instance method
			$manager = $this->init_manager();

			$code         = sanitize_text_field( $_POST['code'] );
			$temp_token   = sanitize_text_field( $_POST['temp_token'] );
			$session_data = self::validate_temp_token( $temp_token );

			if ( ! $session_data ) {
				// Increment failed attempts for rate limiting
				$failed_session = get_transient( 'secnin_2fa_temp_' . $temp_token );
				if ( $failed_session && is_array( $failed_session ) ) {
					self::increment_token_attempts( $temp_token, $failed_session );
				}
				throw new \Exception( __( 'Invalid or expired session.', 'security-ninja' ) );
			}

			$user_id = $session_data['user_id'];

			// Increment attempt counter
			self::increment_token_attempts( $temp_token, $session_data );

			// Check 2FA method and verify accordingly
			$method   = self::get_2fa_method();
			$is_valid = false;

			if ( $method === 'email' ) {
				// Verify email code
				$is_valid = self::validate_email_code( $user_id, $code );
			} else {
				// Verify app code - prioritize temp secret for initial setup
				$temp_secret = get_transient( 'secnin_2fa_temp_secret_' . $temp_token );
				$secret = null;

				if ( $temp_secret ) {
					// Use temporary secret (for initial setup)
					$secret = $temp_secret;
				} else {
					// Fall back to stored user secret (for existing users)
					$encrypted_secret = get_user_meta( $user_id, 'secnin_2fa_secret', true );
					if ( ! empty( $encrypted_secret ) ) {
						$secret = self::decrypt_secret( $encrypted_secret, self::get_passphrase() );
					}
				}

				if ( empty( $secret ) ) {
					throw new \Exception( __( 'No valid 2FA secret found. Please generate a new QR code.', 'security-ninja' ) );
				}

				$is_valid = self::$manager->verify( $code, $secret );
			}

			if ( $is_valid ) {
				// If verification successful and we used a temp secret, store it permanently
				if ( isset( $temp_secret ) && ! empty( $temp_secret ) ) {
					$encrypted_secret = self::encrypt_secret( $temp_secret, self::get_passphrase() );
					update_user_meta( $user_id, 'secnin_2fa_secret', $encrypted_secret );
					delete_transient( 'secnin_2fa_temp_secret_' . $temp_token );
				}

				// Rest of the success handling code...
				update_user_meta( $user_id, 'secnin_2fa_code_validated', 1 );
				update_user_meta( $user_id, 'secnin_2fa_session_validated', time() );
				update_user_meta( $user_id, 'secnin_2fa_setup_complete', 1 );

				$user_data = get_userdata( $user_id );
				if ( false === $user_data ) {
					\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
						'security_ninja',
						'login_2fa_verification_failed',
						/* translators: %d: User ID */
						sprintf( __( 'Failed to retrieve user data for user ID: %d', 'security-ninja' ), $user_id )
					);
					wp_send_json_error( array( 'message' => __( 'Failed to retrieve user data.', 'security-ninja' ) ) );
					return;
				}

				wp_set_current_user( $user_id, $user_data->user_login );
				wp_set_auth_cookie( $user_id, true );
				remove_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10 );
				do_action( 'wp_login', $user_data->user_login, $user_data );
				add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );

				\WPSecurityNinja\Plugin\wf_sn_el_modules::log_event(
					'security_ninja',
					'login_2fa_verification_successful',
					/* translators: %s: User display name */
					sprintf( __( '2FA verification successful for %s', 'security-ninja' ), $user_data->display_name )
				);

				// Get stored redirect URL from session data
				$stored_redirect_to = isset( $session_data['redirect_to'] ) ? $session_data['redirect_to'] : '';
				$redirect_to = self::get_redirect_url( $user_data, $stored_redirect_to );

				wp_send_json_success(
					array(
						'message'  => __( 'Verified. Logging you in.', 'security-ninja' ),
						'redir_to' => esc_url( $redirect_to ),
					)
				);
			} else {
				wp_send_json_error(
					array(
						'message' => __( 'Incorrect 2FA code. Please try again.', 'security-ninja' ),
					)
				);
			}
		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * Checks if 2FA setup is complete for a user.
	 *
	 * @param int $user_id The ID of the user to check.
	 * @return bool True if 2FA setup is complete, false otherwise.
	 */
	public static function is_2fa_setup_complete( $user_id ) {
		if ( ! is_numeric( $user_id ) || $user_id <= 0 ) {
			return false;
		}

		$setup_complete = get_user_meta( $user_id, 'secnin_2fa_setup_complete', true );
		return '1' === $setup_complete;
	}

	/**
	 * Renders the 2FA verification page.
	 *
	 * @author  Lars Koudal
	 * @since   v0.0.1
	 * @version v1.0.1  Tuesday, June 4th, 2024.
	 * @access  public static
	 * @param   int $user_id The ID of the user to verify.
	 * @return  void
	 */
	public static function render_2fa_verify_page( $user, $temp_token = '' ) {
		if ( empty( $temp_token ) ) {
			// Capture redirect_to from request if available
			$redirect_to = '';
			if ( isset( $_REQUEST['redirect_to'] ) ) {
				$redirect_to = esc_url_raw( $_REQUEST['redirect_to'] );
			} elseif ( isset( $_GET['redirect_to'] ) ) {
				$redirect_to = esc_url_raw( $_GET['redirect_to'] );
			} elseif ( isset( $_POST['redirect_to'] ) ) {
				$redirect_to = esc_url_raw( $_POST['redirect_to'] );
			}
			$temp_token = self::create_secure_temp_token( $user->ID, $redirect_to );
		}

		// No nonce needed - temp_token provides security
		$qr_nonce = '';

		if ( ! $user ) {
			return;
		}

		// Check if $user_id is an int value or a user object
		if ( $user instanceof WP_User ) {
			$user_id = $user->ID;
		} elseif ( is_numeric( $user ) ) {
			$user_id = absint( $user );
		} else {
			wp_die( 'Invalid user ID or object.' );
		}
		if ( ! get_userdata( $user_id ) ) {
			wp_die( 'Invalid user ID.' );
		}

		?>
		<script>
			// Enhanced 2FA configuration with error handling
			(function() {
				'use strict';
				
				// Ensure two_factor_auth object is properly defined
				window.two_factor_auth = {
					ajaxurl: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
					temp_token: '<?php echo esc_js( $temp_token ); ?>',
					qr_nonce: '<?php echo esc_js( $qr_nonce ); ?>',
					user_id: '<?php echo esc_js( $user_id ); ?>',
					user_ip: '<?php echo esc_js( isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '' ); ?>',
					user_agent: '<?php echo esc_js( isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : '' ); ?>',
					verify_nonce: '<?php echo esc_js( wp_create_nonce( 'secnin_verify_2fa_' . $user_id ) ); ?>',
					messages: {
						verifying: '<?php echo esc_js( __( 'Checking code...', 'security-ninja' ) ); ?>',
						network_error: '<?php echo esc_js( __( 'Network error. Please check your connection and try again.', 'security-ninja' ) ); ?>',
						qr_error: '<?php echo esc_js( __( 'Error generating QR code. Please try again.', 'security-ninja' ) ); ?>',
						email_error: '<?php echo esc_js( __( 'Error sending email. Please try again.', 'security-ninja' ) ); ?>',
						sending_email: '<?php echo esc_js( __( 'Sending email...', 'security-ninja' ) ); ?>',
						unknown_error: '<?php echo esc_js( __( 'An unknown error occurred. Please try again.', 'security-ninja' ) ); ?>'
					}
				};
				
				// Add error handling for missing configuration
				if (!window.two_factor_auth.ajaxurl || !window.two_factor_auth.temp_token) {
					console.error('2FA: Critical configuration missing');
				}
			})();
		</script>
		<?php

		$script_url = plugins_url( '/js/two-factor-auth.js', __FILE__ );
		$ajax_url   = admin_url( 'admin-ajax.php' );

		// Check if the user has completed 2FA setup
		$user_2fa_setup_complete = self::is_2fa_setup_complete( $user_id );
		$method                  = self::get_2fa_method();

		?>
		<!DOCTYPE html>
		<html <?php language_attributes(); ?>>

		<head>
			<meta http-equiv="Content-Type" content="<?php bloginfo( 'html_type' ); ?>; charset=<?php bloginfo( 'charset' ); ?>" />
			<title><?php esc_html_e( '2FA Verification', 'security-ninja' ); ?></title>
			<?php
			wp_enqueue_style( 'login' );
			wp_enqueue_script( 'jquery' );
			do_action( 'login_enqueue_scripts' );
			do_action( 'login_head' );
			?>
			<style>
				.spinner {
					background: url(<?php echo esc_url( admin_url( 'images/spinner.gif' ) ); ?>) no-repeat;
					background-size: 20px 20px;
					display: none;
					float: right;
					opacity: 0.7;
					width: 20px;
					height: 20px;
					margin: 5px 5px 0;
				}

				.spinner.is-active {
					display: inline-block;
				}

				.qr-code {
					text-align: center;
					margin-bottom: 20px;
				}

				#qr-code-img {
					display: inline-block;
					max-width: 100%;
					height: auto;
				}

				.email-2fa {
					text-align: center;
					margin-bottom: 20px;
					padding: 20px;
					background: #f9f9f9;
					border-radius: 5px;
				}

				.errmsg {
					color: #dc3232;
					font-weight: bold;
				}

				.okmsg {
					color: #46b450;
					font-weight: bold;
				}

				#verify-2fa:disabled {
					opacity: 0.5;
					cursor: not-allowed;
				}
			</style>
			<script src="<?php echo esc_url( $script_url ); ?>"></script>
			<script>
			// Inline handlers for 2FA functionality
			jQuery(document).ready(function($) {
				// Skip handler
				$('.skip-2fa-link').on('click', function(e) {
					e.preventDefault();
					var tempToken = $(this).data('temp-token');
					if (!tempToken) {
						alert('Missing token. Please refresh the page.');
						return;
					}
					
					// Disable the link
					$(this).css('opacity', '0.5').css('pointer-events', 'none');
					
					$.ajax({
						url: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
						type: 'POST',
						data: {
							action: 'secnin_skip_2fa',
							temp_token: tempToken
						},
						success: function(response) {
							if (response && response.success) {
								if (response.data && response.data.redirect_url) {
									window.location.href = response.data.redirect_url;
								} else {
									window.location.reload();
								}
							} else {
								alert(response.data && response.data.message ? response.data.message : 'An error occurred. Please try again.');
								$('.skip-2fa-link').css('opacity', '').css('pointer-events', '');
							}
						},
						error: function() {
							alert('Network error. Please check your connection and try again.');
							$('.skip-2fa-link').css('opacity', '').css('pointer-events', '');
						}
					});
				});
				
				// Input validation handler
				$('#twofa-code').on('input keyup paste', function() {
					var inputValue = $(this).val();
					
					// Remove any non-digit characters
					inputValue = inputValue.replace(/\D/g, '');
					
					// Limit to 6 digits
					inputValue = inputValue.slice(0, 6);
					
					// Update input value
					$(this).val(inputValue);
					
					// Enable/disable submit button based on input validity
					$('#verify-2fa').prop('disabled', inputValue.length !== 6);
				});
				
				// Generate QR code handler
				$('#generate-qr').on('click', function(e) {
					e.preventDefault();
					var $button = $(this);
					var $spinner = $('.qr-code .spinner');
					var $errorContainer = $('#qr-code-img-err');
					
					$button.prop('disabled', true);
					$spinner.addClass('is-active');
					$errorContainer.empty();
					
					$.ajax({
						url: '<?php echo esc_js( admin_url( 'admin-ajax.php' ) ); ?>',
						type: 'POST',
						data: {
							action: 'secnin_generate_qr_code',
							temp_token: '<?php echo esc_js( $temp_token ); ?>'
						},
						success: function(response) {
							$button.prop('disabled', false);
							$spinner.removeClass('is-active');
							
							if (response && response.success && response.data && response.data.qr_code) {
								$errorContainer.empty();
								$('#qr-code-img').attr('src', response.data.qr_code);
								
								if (response.data.secret) {
									$('#secret-key').text('Secret: ' + response.data.secret);
									$('#secret-container').show();
								}
							} else {
								var errorMsg = (response && response.data && response.data.message) ? 
									response.data.message : 
									'Error generating QR code. Please try again.';
								$errorContainer.html('<p class="errmsg">' + errorMsg + '</p>');
							}
						},
						error: function() {
							$button.prop('disabled', false);
							$spinner.removeClass('is-active');
							$errorContainer.html('<p class="errmsg">Network error. Please check your connection and try again.</p>');
						}
					});
				});		
			});
			</script>
		</head>

		<body class="login js login-action-2fa_verification wp-core-ui">
			<div id="login">

				<?php if ( $method === 'email' ) : ?>
					<div class="email-2fa">
						<h3><?php esc_html_e( 'Email Verification', 'security-ninja' ); ?></h3>
						<p><?php esc_html_e( 'A verification code has been sent to your email address.', 'security-ninja' ); ?></p>
						<button id="send-email" class="button button-secondary"><?php esc_html_e( 'Send New Code', 'security-ninja' ); ?></button>
						<div id="email-status"></div>
					</div>
				<?php elseif ( ! $user_2fa_setup_complete ) : ?>
					<div class="qr-code">
						<img id="qr-code-img" 
							src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'%3E%3Crect width='200' height='200' fill='%23ffffff' stroke='%23eee' stroke-width='2'/%3E%3C/svg%3E" 
							alt="<?php esc_attr_e( 'QR Code Loading', 'security-ninja' ); ?>" 
							style="display: inline-block; width: 200px; height: 200px; margin-bottom: 10px;" />


				<p id="secret-key"></p>
							
						
						<div id="qr-code-img-err"></div>
						<div class="spinner"></div>
						<button id="generate-qr" class="button button-secondary"><?php esc_html_e( 'Generate New QR Code', 'security-ninja' ); ?></button>
					</div>



					
				<?php endif; ?>

				<form name="twofa-form-verify" id="twofa-form-verify" action="" method="post">
					<p>
						<label for="twofa-code">
							<?php if ( $method === 'email' ) : ?>
								<?php esc_html_e( 'Enter the 6-digit code sent to your email.', 'security-ninja' ); ?>
							<?php else : ?>
								<?php esc_html_e( 'Enter the 6-digit code from your authentication app.', 'security-ninja' ); ?>
							<?php endif; ?>
						</label>
						<input type="text" name="twofa-code" id="twofa-code" class="input" value="" size="6" maxlength="6" pattern="\d{6}" inputmode="numeric" autocapitalize="off" autocomplete="off" data-1p-ignore required />
					</p>
					<div id="twofa-verify-msg"></div>
					<p class="submit">
						<input type="submit" name="wp-submit" id="verify-2fa" class="button button-primary button-large" value="<?php esc_attr_e( 'Verify Code', 'security-ninja' ); ?>" disabled />
					</p>

					<?php

					$my_options = \WPSecurityNinja\Plugin\Wf_sn_cf::get_options();
					$grace_period = isset( $my_options['2fa_grace_period'] ) ? intval( $my_options['2fa_grace_period'] ) : 0;
					$enabled_timestamp = isset( $my_options['2fa_enabled_timestamp'] ) ? intval( $my_options['2fa_enabled_timestamp'] ) : 0;

					if ( 0 === $enabled_timestamp ) {
						$enabled_timestamp                   = time();
						$my_options['2fa_enabled_timestamp'] = $enabled_timestamp;
						update_option( 'wf_sn_cf', $my_options ); // Save the updated options
					}
					$current_time = time();
					$time_left = ( $enabled_timestamp + ( $grace_period * DAY_IN_SECONDS ) ) - $current_time;


					if ( $grace_period > 0 && $time_left > 0 && ! self::is_user_using_two_factor( $user->ID ) ) :
						$days_left  = floor( $time_left / 86400 );
						$hours_left = floor( ( $time_left % 86400 ) / 3600 );
						?>
						<p class="grace-period-notice">
							<?php
							printf(
								/* translators: %1$d: number of days left, %2$d: number of hours left */
								esc_html__( 'You have %1$d days and %2$d hours left to set up 2FA. ', 'security-ninja' ),
								esc_html( $days_left ),
								esc_html( $hours_left )
							);
							?>
							<a href="#" class="skip-2fa-link" data-temp-token="<?php echo esc_attr( $temp_token ); ?>">
								<?php esc_html_e( 'Skip for now', 'security-ninja' ); ?>
							</a>
						</p>
					<?php endif; ?>
				</form>

				<p id="backtoblog">
					<a href="<?php echo esc_url( home_url( '/' ) ); ?>">
						<?php
						/* translators: %s: Site title. */
						printf( esc_html_x( '&larr; Back to %s', 'security-ninja' ), esc_html( get_bloginfo( 'title', 'display' ) ) );
						?>
					</a>
				</p>
			</div>

			<?php
			do_action( 'login_footer' );
			?>
			<div class="clear"></div>
			<script>
						document.addEventListener('DOMContentLoaded', function() {
							document.getElementById('twofa-code').focus();
				});



			</script>
		</body>

		</html>
		<?php
	}


	/**
	 * Collect authentication cookies for later removal.
	 *
	 * This method parses the provided cookie string and stores the authentication
	 * token if present. These tokens will be used later to invalidate sessions
	 * during the two-factor authentication process.
	 *
	 * @since 1.0.0
	 *
	 * @param string $cookie The authentication cookie string.
	 * @return void
	 */
	public static function collect_auth_cookie_tokens( $cookie ) {
		if ( ! is_string( $cookie ) || empty( $cookie ) ) {
			return;
		}

		$parsed = wp_parse_auth_cookie( $cookie );
		if ( is_array( $parsed ) && ! empty( $parsed['token'] ) ) {
			self::$password_auth_tokens[] = $parsed['token'];
		}
	}


	/**
	 * Check if the user is using two-factor authentication.
	 *
	 * This method checks if a user has a 2FA secret key set in their user meta.
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id The ID of the user to check.
	 * @return bool True if the user has a 2FA secret key set, false otherwise.
	 */
	private static function is_user_using_two_factor( $user_id ) {
		if ( ! is_numeric( $user_id ) || $user_id <= 0 ) {
			return false;
		}

		$secret_key = get_user_meta( $user_id, 'secnin_2fa_secret', true );
		return ! empty( $secret_key );
	}
	/**
	 * Check if the user should be using two-factor authentication.
	 *
	 * This method determines whether a user should be required to use 2FA based on their
	 * bypass status and role.
	 *
	 * @since 1.0.0
	 *
	 * @param int $user_id The ID of the user to check.
	 * @return bool True if the user should use 2FA, false otherwise.
	 */
	private static function should_user_use_two_factor( $user_id ) {
		if ( ! is_numeric( $user_id ) || $user_id <= 0 ) {
			return false;
		}

		$bypass_2fa = get_user_meta( $user_id, 'bypass_2fa', true );
		if ( '1' === $bypass_2fa ) {
			return false;
		}

		$user = get_userdata( $user_id );
		if ( ! $user || ! $user instanceof \WP_User ) {
			return false;
		}

		$my_options     = self::get_options();
		$required_roles = isset( $my_options['2fa_required_roles'] ) ? (array) $my_options['2fa_required_roles'] : array();

		return ! empty( array_intersect( $required_roles, $user->roles ) );
	}

	/**
	 * Handles session management for 2FA authentication.
	 *
	 * @since 1.0.0
	 * @param WP_User $user User object.
	 * @param bool    $is_skipping Whether this is a skip 2FA operation.
	 * @return void
	 */
	private static function handle_2fa_session( $user, $is_skipping = false ) {
		if ( ! $user instanceof \WP_User ) {
			return;
		}

		if ( $is_skipping ) {
			// For skipping, do nothing - let the normal login flow continue
			return;
		} else {
			// For regular 2FA flow, use full logout
			wp_logout();
			// Restore user context for 2FA
			wp_set_current_user( $user->ID );
		}
	}

	public function ajax_skip_2fa() {
		try {
			if ( empty( $_POST['temp_token'] ) ) {
				throw new \Exception( __( 'Missing temp_token', 'security-ninja' ) );
			}

			$temp_token = sanitize_text_field( $_POST['temp_token'] );

			// No nonce needed - temp_token provides security
			$session_data = self::validate_temp_token( $temp_token );

			if ( ! $session_data ) {
				throw new \Exception( __( 'Invalid or expired session', 'security-ninja' ) );
			}

			$user_id = $session_data['user_id'];

			$user = get_user_by( 'ID', $user_id );
			if ( ! $user ) {
				throw new \Exception( __( 'User not found', 'security-ninja' ) );
			}

			// Set up proper login session
			wp_set_current_user( $user_id, $user->user_login );
			wp_set_auth_cookie( $user_id, true );

			// Remove our wp_login hook temporarily to avoid recursion
			remove_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10 );

			// Fire wp_login action to ensure all login hooks run
			do_action( 'wp_login', $user->user_login, $user );

			// Re-add our hook
			add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );

			// Remove the temporary tokens
			delete_transient( 'secnin_2fa_temp_' . $temp_token );
			delete_transient( 'secnin_2fa_temp_secret_' . $temp_token );

			// Get stored redirect URL from session data
			$stored_redirect_to = isset( $session_data['redirect_to'] ) ? $session_data['redirect_to'] : '';
			// Get proper redirect URL based on user capabilities
			$redirect_to = self::get_redirect_url( $user, $stored_redirect_to );

			$my_options        = self::get_options();
			$enabled_timestamp = isset( $my_options['2fa_enabled_timestamp'] ) ? intval( $my_options['2fa_enabled_timestamp'] ) : 0;
			$grace_period      = isset( $my_options['2fa_grace_period'] ) ? intval( $my_options['2fa_grace_period'] ) : 0;
			$current_time      = time();
			$time_left         = ( $enabled_timestamp + ( $grace_period * DAY_IN_SECONDS ) ) - $current_time;

			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_skipped',
				sprintf(
					/* translators: %1$d: number of days left, %2$d: number of hours left */
					__( 'User skipped 2FA setup. Time left to activate: %1$d days and %2$d hours.', 'security-ninja' ),
					floor( $time_left / 86400 ),
					floor( ( $time_left % 86400 ) / 3600 )
				)
			);
			wp_send_json_success(
				array(
					'message'      => __( 'Continuing to dashboard...', 'security-ninja' ),
					'redirect_url' => esc_url( $redirect_to ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * Create a secure temp token with session binding
	 *
	 * @param int $user_id User ID
	 * @return string Secure temp token
	 */
	private static function create_secure_temp_token( $user_id, $redirect_to = '' ) {
		$base_token   = wp_generate_password( 32, false );
		
		// Capture redirect_to from request if not provided and available
		if ( empty( $redirect_to ) ) {
			$redirect_to = isset( $_REQUEST['redirect_to'] ) ? esc_url_raw( $_REQUEST['redirect_to'] ) : '';
			// Also check $_GET and $_POST for compatibility
			if ( empty( $redirect_to ) && isset( $_GET['redirect_to'] ) ) {
				$redirect_to = esc_url_raw( $_GET['redirect_to'] );
			}
			if ( empty( $redirect_to ) && isset( $_POST['redirect_to'] ) ) {
				$redirect_to = esc_url_raw( $_POST['redirect_to'] );
			}
		}
		
		$session_data = array(
			'user_id'      => $user_id,
			'ip'           => self::get_client_ip(),
			'user_agent'   => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255 ),
			'created'      => time(),
			'attempts'     => 0,
			'max_attempts' => 5,
			'redirect_to'  => $redirect_to,
		);

		// Store with shorter expiry for security
		set_transient( 'secnin_2fa_temp_' . $base_token, $session_data, 15 * MINUTE_IN_SECONDS );

		return $base_token;
	}

	/**
	 * Validate temp token with security checks
	 *
	 * @param string $temp_token Token to validate
	 * @return array|false Session data if valid, false if invalid
	 */
	private static function validate_temp_token( $temp_token ) {
		if ( empty( $temp_token ) ) {
			return false;
		}

		$session_data = get_transient( 'secnin_2fa_temp_' . $temp_token );
		if ( ! $session_data || ! is_array( $session_data ) ) {
			return false;
		}

		// Check IP address
		$current_ip = self::get_client_ip();
		if ( $session_data['ip'] !== $current_ip ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_token_ip_mismatch',
				sprintf(
					/* translators: %1$s: Original IP address, %2$s: Current IP address */
					__( '2FA token used from different IP. Original: %1$s, Current: %2$s', 'security-ninja' ),
					$session_data['ip'],
					$current_ip
				)
			);
			delete_transient( 'secnin_2fa_temp_' . $temp_token );
			return false;
		}

		// Check user agent
		$current_ua = substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255 );
		if ( $session_data['user_agent'] !== $current_ua ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_token_ua_mismatch',
				__( '2FA token used from different user agent', 'security-ninja' )
			);
			delete_transient( 'secnin_2fa_temp_' . $temp_token );
			return false;
		}

		// Check rate limiting
		if ( $session_data['attempts'] >= $session_data['max_attempts'] ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_token_rate_limited',
				/* translators: %d: number of attempts */
				sprintf( __( '2FA token rate limited after %d attempts', 'security-ninja' ), $session_data['attempts'] )
			);
			delete_transient( 'secnin_2fa_temp_' . $temp_token );
			return false;
		}

		// Check age (additional security - tokens should be short-lived)
		if ( ( time() - $session_data['created'] ) > ( 15 * MINUTE_IN_SECONDS ) ) {
			delete_transient( 'secnin_2fa_temp_' . $temp_token );
			return false;
		}

		return $session_data;
	}

	/**
	 * Increment attempt counter for temp token
	 *
	 * @param string $temp_token Token to increment attempts for
	 * @param array $session_data Current session data
	 */
	private static function increment_token_attempts( $temp_token, $session_data ) {
		++$session_data['attempts'];
		set_transient( 'secnin_2fa_temp_' . $temp_token, $session_data, 15 * MINUTE_IN_SECONDS );
	}

	/**
	 * Get appropriate redirect URL for user after 2FA verification
	 *
	 * @param WP_User $user User object
	 * @param string  $stored_redirect_to Stored redirect URL from session
	 * @return string Redirect URL
	 */
	private static function get_redirect_url( $user, $stored_redirect_to = '' ) {
		$default = admin_url();
		// Validate stored redirect with WordPress core API; use default when empty or invalid.
		$redirect_to = ! empty( $stored_redirect_to )
			? wp_validate_redirect( esc_url_raw( $stored_redirect_to ), $default )
			: $default;
		// Same signature as WordPress core: $redirect_to, $requested_redirect_to, $user.
		return apply_filters( 'login_redirect', $redirect_to, $stored_redirect_to, $user );
	}

	/**
	 * Get client IP address with proxy support
	 *
	 * @return string Client IP address
	 */
	private static function get_client_ip() {
		$ip_headers = array(
			'HTTP_X_FORWARDED_FOR',
			'HTTP_X_REAL_IP',
			'HTTP_CLIENT_IP',
			'REMOTE_ADDR',
		);

		foreach ( $ip_headers as $header ) {
			if ( ! empty( $_SERVER[ $header ] ) ) {
				$ips = explode( ',', $_SERVER[ $header ] );
				$ip  = trim( $ips[0] );
				if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
					return $ip;
				}
			}
		}

		return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
	}

	/**
	 * Create a short-lived nonce for sensitive 2FA operations
	 *
	 * @param string $action Nonce action
	 * @return string Nonce value
	 */
	private static function create_short_nonce( $action ) {
		// Create a more secure nonce that's only valid for 5 minutes
		$time = ceil( time() / 300 ); // 5-minute windows
		return wp_hash( $action . '|' . $time . '|' . get_current_user_id(), 'nonce' );
	}

	/**
	 * Verify short-lived nonce
	 *
	 * @param string $nonce Nonce to verify
	 * @param string $action Nonce action
	 * @return bool True if valid, false otherwise
	 */
	private static function verify_short_nonce( $nonce, $action ) {
		$time = ceil( time() / 300 ); // Current 5-minute window

		// Check current and previous window (10 minutes total)
		for ( $i = 0; $i <= 1; $i++ ) {
			$expected = wp_hash( $action . '|' . ( $time - $i ) . '|' . get_current_user_id(), 'nonce' );
			if ( hash_equals( $expected, $nonce ) ) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Generate a secure 6-digit code for email 2FA
	 *
	 * @param int $user_id User ID
	 * @return string 6-digit code
	 */
	private static function generate_email_code( $user_id ) {
		// Generate a secure 6-digit code
		$code = sprintf( '%06d', wp_rand( 0, 999999 ) );

		// Store the code with user ID and expiry
		$code_data = array(
			'user_id'    => $user_id,
			'code'       => $code,
			'created'    => time(),
			'expires'    => time() + ( 15 * MINUTE_IN_SECONDS ), // 15 minutes
			'ip'         => self::get_client_ip(),
			'user_agent' => substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255 ),
		);

		// Store with 15-minute expiry
		set_transient( 'secnin_2fa_email_' . $user_id, $code_data, 15 * MINUTE_IN_SECONDS );

		return $code;
	}

	/**
	 * Send 2FA email with code
	 *
	 * @param int $user_id User ID
	 * @param string $code 6-digit code
	 * @return bool True if email sent successfully, false otherwise
	 */
	private static function send_2fa_email( $user_id, $code ) {
		$user = get_userdata( $user_id );
		if ( ! $user ) {
			return false;
		}

		$site_name         = get_bloginfo( 'name' );
		$site_url          = home_url();
		$user_display_name = $user->display_name;
		$user_email        = $user->user_email;

		// Email subject
		$subject = sprintf(
			/* translators: %s: Site name */
			__( 'Your 2FA code for %s', 'security-ninja' ),
			$site_name
		);

		// Email headers
		$headers = array( 'Content-Type: text/html; charset=UTF-8' );

		// Email content
		$message = sprintf(
			'<html><body>
			<h2>%s</h2>
			<p>%s</p>
			<div style="background: #f5f5f5; padding: 20px; border-radius: 5px; text-align: center; margin: 20px 0;">
				<h1 style="font-size: 32px; letter-spacing: 5px; color: #333; margin: 0;">%s</h1>
			</div>
			<p><strong>%s</strong></p>
			<ul>
				<li>%s</li>
				<li>%s</li>
				<li>%s</li>
			</ul>
			<p><em>%s</em></p>
			</body></html>',
			sprintf(
				/* translators: %s: Site name */
				esc_html__( 'Two-Factor Authentication Code for %s', 'security-ninja' ),
				esc_html( $site_name )
			),
			sprintf(
				/* translators: %s: User display name */
				esc_html__( 'Hello %s,', 'security-ninja' ),
				esc_html( $user_display_name )
			),
			esc_html( $code ),
			esc_html__( 'This code will expire in 15 minutes.', 'security-ninja' ),
			sprintf(
				/* translators: %s: Site URL */
				esc_html__( 'Site: %s', 'security-ninja' ),
				esc_html( $site_url )
			),
			sprintf(
				/* translators: %s: User email */
				esc_html__( 'Email: %s', 'security-ninja' ),
				esc_html( $user_email )
			),
			esc_html__( 'Time: ' ) . current_time( 'Y-m-d H:i:s T' ),
			esc_html__( 'If you did not request this code, please ignore this email and contact your administrator immediately.', 'security-ninja' )
		);

		// Set HTML content type
		add_filter( 'wp_mail_content_type', array( __CLASS__, 'set_html_content_type' ) );

		// Send email
		$sent = wp_mail( $user_email, $subject, $message, $headers );

		// Remove HTML content type filter
		remove_filter( 'wp_mail_content_type', array( __CLASS__, 'set_html_content_type' ) );

		// Log the attempt
		if ( $sent ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_email_sent',
				sprintf(
					/* translators: %s: User email */
					__( '2FA email code sent to %s', 'security-ninja' ),
					$user_email
				)
			);
		} else {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_email_failed',
				sprintf(
					/* translators: %s: User email */
					__( 'Failed to send 2FA email code to %s', 'security-ninja' ),
					$user_email
				)
			);
		}

		return $sent;
	}

	/**
	 * Set HTML content type for emails
	 *
	 * @return string
	 */
	public static function set_html_content_type() {
		return 'text/html';
	}

	/**
	 * Validate email 2FA code
	 *
	 * @param int $user_id User ID
	 * @param string $code Submitted code
	 * @return bool True if valid, false otherwise
	 */
	private static function validate_email_code( $user_id, $code ) {
		$code_data = get_transient( 'secnin_2fa_email_' . $user_id );

		if ( ! $code_data || ! is_array( $code_data ) ) {
			return false;
		}

		// Check if code matches
		if ( $code_data['code'] !== $code ) {
			return false;
		}

		// Check if expired
		if ( time() > $code_data['expires'] ) {
			delete_transient( 'secnin_2fa_email_' . $user_id );
			return false;
		}

		// Check IP address
		$current_ip = self::get_client_ip();
		if ( $code_data['ip'] !== $current_ip ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_email_ip_mismatch',
				sprintf(
					/* translators: %1$s: Original IP address, %2$s: Current IP address */
					__( '2FA email code used from different IP. Original: %1$s, Current: %2$s', 'security-ninja' ),
					$code_data['ip'],
					$current_ip
				)
			);
			delete_transient( 'secnin_2fa_email_' . $user_id );
			return false;
		}

		// Check user agent
		$current_ua = substr( $_SERVER['HTTP_USER_AGENT'] ?? '', 0, 255 );
		if ( $code_data['user_agent'] !== $current_ua ) {
			wf_sn_el_modules::log_event(
				'security_ninja',
				'2fa_email_ua_mismatch',
				__( '2FA email code used from different user agent', 'security-ninja' )
			);
			delete_transient( 'secnin_2fa_email_' . $user_id );
			return false;
		}

		// Code is valid - clean up
		delete_transient( 'secnin_2fa_email_' . $user_id );
		return true;
	}

	/**
	 * Get 2FA method for user/site
	 *
	 * @return string 'app' or 'email'
	 */
	private static function get_2fa_method() {
		$options = self::get_options();
		if ( isset( $options['2fa_methods'] ) ) {
			if ( is_array( $options['2fa_methods'] ) && ! empty( $options['2fa_methods'] ) ) {
				return $options['2fa_methods'][0];
			} elseif ( ! is_array( $options['2fa_methods'] ) ) {
				return $options['2fa_methods'];
			}
		}
		return 'app';
	}

	/**
	 * AJAX handler for sending 2FA email
	 */
	public function ajax_send_2fa_email() {
		try {
			if ( empty( $_POST['temp_token'] ) ) {
				throw new \Exception( __( 'Missing temp_token', 'security-ninja' ) );
			}

			$temp_token   = sanitize_text_field( $_POST['temp_token'] );
			$session_data = self::validate_temp_token( $temp_token );

			if ( ! $session_data ) {
				throw new \Exception( __( 'Invalid or expired session', 'security-ninja' ) );
			}

			$user_id = $session_data['user_id'];

			// Check if email method is enabled
			if ( self::get_2fa_method() !== 'email' ) {
				throw new \Exception( __( 'Email 2FA is not enabled', 'security-ninja' ) );
			}

			// Check for rate limiting on email sending (prevent multiple emails within 30 seconds)
			$email_rate_limit_key = 'secnin_2fa_email_rate_' . $user_id;
			$last_email_time = get_transient( $email_rate_limit_key );
			
			if ( $last_email_time && ( time() - $last_email_time ) < 30 ) {
				$time_left = 30 - ( time() - $last_email_time );
				throw new \Exception( 
					sprintf( 
						/* translators: %d: seconds remaining */
						__( 'Please wait %d seconds before requesting another email code.', 'security-ninja' ), 
						$time_left 
					) 
				);
			}

			// Check if there's already a valid email code that hasn't expired
			$existing_code_data = get_transient( 'secnin_2fa_email_' . $user_id );
			if ( $existing_code_data && is_array( $existing_code_data ) && time() < $existing_code_data['expires'] ) {
				// Resend the existing code instead of generating a new one
				$code = $existing_code_data['code'];
				$sent = self::send_2fa_email( $user_id, $code );
			} else {
				// Generate and send new email code
				$code = self::generate_email_code( $user_id );
				$sent = self::send_2fa_email( $user_id, $code );
			}

			if ( ! $sent ) {
				throw new \Exception( __( 'Failed to send email. Please try again.', 'security-ninja' ) );
			}

			// Set rate limiting for email sending
			set_transient( $email_rate_limit_key, time(), 30 );

			// Increment attempt counter for rate limiting
			self::increment_token_attempts( $temp_token, $session_data );

			wp_send_json_success(
				array(
					'message' => __( 'Email sent successfully. Please check your inbox.', 'security-ninja' ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * AJAX handler for admin QR code generation
	 */
	public function ajax_admin_generate_qr() {
		try {
			// Verify nonce and permissions
			if ( ! isset( $_POST['nonce'] ) || ! isset( $_POST['user_id'] ) ) {
				throw new \Exception( __( 'Missing required parameters', 'security-ninja' ) );
			}

			$user_id = intval( $_POST['user_id'] );
			$nonce = sanitize_text_field( $_POST['nonce'] );

			if ( ! wp_verify_nonce( $nonce, 'admin_2fa_setup_' . $user_id ) ) {
				throw new \Exception( __( 'Invalid nonce', 'security-ninja' ) );
			}

			if ( ! current_user_can( 'edit_user', $user_id ) ) {
				throw new \Exception( __( 'Insufficient permissions', 'security-ninja' ) );
			}

			$user = get_user_by( 'ID', $user_id );
			if ( ! $user ) {
				throw new \Exception( __( 'User not found', 'security-ninja' ) );
			}

			// Initialize manager
			$manager = $this->init_manager();

			// Generate new secret
			$secret = $manager->generateSecretKey();

			// Store temporary secret for this admin session
			$temp_key = 'admin_2fa_temp_secret_' . get_current_user_id() . '_' . $user_id;
			if ( ! set_transient( $temp_key, $secret, 30 * MINUTE_IN_SECONDS ) ) {
				throw new \Exception( __( 'Failed to store secret', 'security-ninja' ) );
			}

			// Get site info for the label
		$site_url  = Wf_Sn_Vu::remove_http( site_url() );

		// Create the otpauth URI
		// Format: site_url:username (e.g., "example.com:john")
		$label   = rawurlencode( $site_url );
		$issuer  = rawurlencode( $site_url );
		$account = rawurlencode( $user->user_login );

			$uri = sprintf(
				'otpauth://totp/%s:%s?secret=%s&issuer=%s',
				$label,
				$account,
				$secret,
				$issuer
			);

			// Generate QR code
			$options = new QROptions(
				array(
					'outputType'  => QRCode::OUTPUT_MARKUP_SVG,
					'imageBase64' => true,
				)
			);

			$qrcode           = new QRCode( $options );
			$qr_code_data_uri = $qrcode->render( $uri );

			wp_send_json_success(
				array(
					'qr_code' => $qr_code_data_uri,
					'secret'  => $secret,
					'message' => __( 'QR code generated successfully', 'security-ninja' ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * AJAX handler for admin 2FA verification
	 */
	public function ajax_admin_verify_2fa() {
		try {
			// Verify nonce and permissions
			if ( ! isset( $_POST['nonce'] ) || ! isset( $_POST['user_id'] ) || ! isset( $_POST['code'] ) ) {
				throw new \Exception( __( 'Missing required parameters', 'security-ninja' ) );
			}

			$user_id = intval( $_POST['user_id'] );
			$nonce = sanitize_text_field( $_POST['nonce'] );
			$code = sanitize_text_field( $_POST['code'] );

			if ( ! wp_verify_nonce( $nonce, 'admin_2fa_setup_' . $user_id ) ) {
				throw new \Exception( __( 'Invalid nonce', 'security-ninja' ) );
			}

			if ( ! current_user_can( 'edit_user', $user_id ) ) {
				throw new \Exception( __( 'Insufficient permissions', 'security-ninja' ) );
			}

			$user = get_user_by( 'ID', $user_id );
			if ( ! $user ) {
				throw new \Exception( __( 'User not found', 'security-ninja' ) );
			}

			// Get temporary secret
			$temp_key = 'admin_2fa_temp_secret_' . get_current_user_id() . '_' . $user_id;
			$secret = get_transient( $temp_key );

			if ( ! $secret ) {
				throw new \Exception( __( 'No QR code generated. Please generate a new QR code first.', 'security-ninja' ) );
			}

			// Initialize manager and verify code
			$manager = $this->init_manager();
			$is_valid = $manager->verify( $code, $secret );

			if ( $is_valid ) {
				// Store the secret permanently
				$encrypted_secret = self::encrypt_secret( $secret, self::get_passphrase() );
				update_user_meta( $user_id, 'secnin_2fa_secret', $encrypted_secret );
				update_user_meta( $user_id, 'secnin_2fa_setup_complete', 1 );

				// Clean up temporary secret
				delete_transient( $temp_key );

				// Log the event
				wf_sn_el_modules::log_event(
					'security_ninja',
					'admin_2fa_setup_completed',
					sprintf(
						/* translators: %1$s: User display name, %2$d: User ID */
						__( 'Admin completed 2FA setup for user %1$s (ID: %2$d)', 'security-ninja' ),
						$user->display_name,
						$user_id
					)
				);

				wp_send_json_success(
					array(
						'message' => __( '2FA setup completed successfully!', 'security-ninja' ),
					)
				);
			} else {
				wp_send_json_error(
					array(
						'message' => __( 'Invalid code. Please try again.', 'security-ninja' ),
					)
				);
			}

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}

	/**
	 * AJAX handler for admin 2FA reset
	 */
	public function ajax_admin_reset_2fa() {
		try {
			// Verify nonce and permissions
			if ( ! isset( $_POST['nonce'] ) || ! isset( $_POST['user_id'] ) ) {
				throw new \Exception( __( 'Missing required parameters', 'security-ninja' ) );
			}

			$user_id = intval( $_POST['user_id'] );
			$nonce = sanitize_text_field( $_POST['nonce'] );

			if ( ! wp_verify_nonce( $nonce, 'admin_2fa_setup_' . $user_id ) ) {
				throw new \Exception( __( 'Invalid nonce', 'security-ninja' ) );
			}

			if ( ! current_user_can( 'edit_user', $user_id ) ) {
				throw new \Exception( __( 'Insufficient permissions', 'security-ninja' ) );
			}

			$user = get_user_by( 'ID', $user_id );
			if ( ! $user ) {
				throw new \Exception( __( 'User not found', 'security-ninja' ) );
			}

			// Remove 2FA data
			delete_user_meta( $user_id, 'secnin_2fa_secret' );
			delete_user_meta( $user_id, 'secnin_2fa_setup_complete' );
			delete_user_meta( $user_id, 'secnin_2fa_code_validated' );
			delete_user_meta( $user_id, 'secnin_2fa_session_validated' );

			// Clean up any temporary secrets
			$temp_key = 'admin_2fa_temp_secret_' . get_current_user_id() . '_' . $user_id;
			delete_transient( $temp_key );

			// Log the event
			wf_sn_el_modules::log_event(
				'security_ninja',
				'admin_2fa_reset',
				sprintf(
					/* translators: %1$s: User display name, %2$d: User ID */
					__( 'Admin reset 2FA for user %1$s (ID: %2$d)', 'security-ninja' ),
					$user->display_name,
					$user_id
				)
			);

			wp_send_json_success(
				array(
					'message' => __( '2FA has been reset successfully.', 'security-ninja' ),
				)
			);

		} catch ( \Exception $e ) {
			wp_send_json_error( array( 'message' => $e->getMessage() ) );
		}
		exit;
	}
}

// Initialize the plugin
add_action(
	'plugins_loaded',
	function () {
		Wf_Sn_2fa::init();
	}
);