<?php
namespace WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Providers;

use OTPHP\HOTP;
use WPUltimateSecurity\UltimateSecurity\Contracts\TwoFactorProviderInterface;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Services\EncryptionService;
use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Helpers\QRCodeGenerator;

/**
 * HOTP (HMAC-based One-Time Password) Provider
 *
 * Handles HOTP-based two-factor authentication with counter.
 * Implements TwoFactorProviderInterface for consistent 2FA provider API.
 *
 * @implements TwoFactorProviderInterface
 */
class HOTPProvider implements TwoFactorProviderInterface
{
	/**
	 * Provider slug for internal use
	 */
	private const PROVIDER_SLUG = 'hotp';

	/**
	 * Settings key in plugin options
	 */
	private const SETTINGS_KEY = '2fa_hotp';

	// =========================================================================
	// TwoFactorProviderInterface Implementation
	// =========================================================================

	/**
	 * {@inheritdoc}
	 */
	public function isEnabledForUser(int $userId): bool
	{
		return self::is_configured($userId);
	}

	/**
	 * {@inheritdoc}
	 */
	public function enableForUser(int $userId): bool
	{
		// Generate HOTP secret if not exists
		$data = self::get_otp_data($userId);
		return $data !== false;
	}

	/**
	 * {@inheritdoc}
	 */
	public function disableForUser(int $userId): bool
	{
		return self::disable_for_user($userId);
	}

	/**
	 * {@inheritdoc}
	 *
	 * HOTP codes are generated on the user's device, so this is a no-op.
	 * Returns true to indicate the provider is ready to verify codes.
	 */
	public function sendCode(int $userId): bool
	{
		// HOTP doesn't send codes - they're generated by the authenticator app
		return true;
	}

	/**
	 * {@inheritdoc}
	 */
	public function verifyCode(int $userId, string $code): bool
	{
		return self::verify_code_with_window($userId, $code);
	}

	/**
	 * {@inheritdoc}
	 */
	public function getProviderName(): string
	{
		return __('Authenticator App (HOTP)', 'ultimate-security');
	}

	/**
	 * {@inheritdoc}
	 */
	public function getProviderSlug(): string
	{
		return self::PROVIDER_SLUG;
	}

	/**
	 * {@inheritdoc}
	 */
	public function getSettingsKey(): string
	{
		return self::SETTINGS_KEY;
	}

	// =========================================================================
	// Static Methods (Backward Compatibility)
	// =========================================================================

	/**
	 * Get HOTP OTP code and provisioning data
	 *
	 * @param int $user_id User ID
	 * @return array|false Array with hotp_secret, hotp_code, hotp_qr_code or false
	 */
	public static function get_otp_data($user_id)
	{
		$user = get_userdata($user_id);
		if ($user === false) {
			return false;
		}

		$is_encryption_enabled = EncryptionService::is_encryption_enabled();
		$hotp_secret = get_user_meta($user->ID, 'ultimate_security_2fa_hotp_secret', true);

		if (!$hotp_secret) {
			$hotp = HOTP::create();
			$raw_hotp_secret = $hotp->getSecret();
			$hotp_secret = $is_encryption_enabled ? EncryptionService::encrypt($raw_hotp_secret) : $raw_hotp_secret;
			update_user_meta($user->ID, 'ultimate_security_2fa_hotp_secret', $hotp_secret);
		}

		// Retrieve or initialize the counter
		$hotp_counter = get_user_meta($user->ID, 'ultimate_security_2fa_hotp_counter', true);
		if ( $hotp_counter === '' || $hotp_counter === false ) {
			$hotp_counter = 0;
			update_user_meta($user->ID, 'ultimate_security_2fa_hotp_counter', $hotp_counter);
		}

		// Create the HOTP object
		$original_hotp_secret = $is_encryption_enabled ? EncryptionService::decrypt($hotp_secret) : $hotp_secret;

		if ($original_hotp_secret === false) {
			// handle decrypt failure — force re-enroll or return an error
			return false;
		}

		$hotp = HOTP::create($original_hotp_secret);
		$hotp->setLabel($user->user_login);
		$hotp->setIssuer(get_bloginfo('name'));

		// Always use counter 0 for QR code provisioning URI so authenticator apps start from 0
		$hotp->setCounter(0);

		// Generate the QR code URL using external API (consistent with TOTP)
		// $hotp_qr_code_uri = sprintf(
		// 	'https://api.qrserver.com/v1/create-qr-code/?data=%s&size=200x200',
		// 	urlencode($hotp->getProvisioningUri())
		// );

		// Generate the QR code locally (alternative - commented out)
		$hotp_qr_code_uri = QRCodeGenerator::generate($hotp->getProvisioningUri());

		// Generate the current OTP code at the CURRENT counter (not 0!)
		// This is critical for HOTP synchronization
		$hotp_current_code = $hotp->at((int)$hotp_counter);

		return [
			'hotp_secret' => esc_html($original_hotp_secret),
			'hotp_code' => esc_html($hotp_current_code),
			'hotp_qr_code' => $hotp_qr_code_uri,
			'hotp_counter' => (int)$hotp_counter,
		];
	}

	/**
	 * Generate next HOTP code and increment counter
	 *
	 * This method is called when the user explicitly requests a new code.
	 * It increments the counter, matching the event-based nature of HOTP.
	 *
	 * @param int $user_id User ID
	 * @return array|false Array with next code and counter or false
	 */
	public static function generate_next_code($user_id)
	{
		$user = get_userdata($user_id);
		if ($user === false) {
			return false;
		}

		$is_encryption_enabled = EncryptionService::is_encryption_enabled();
		$hotp_secret = get_user_meta($user->ID, 'ultimate_security_2fa_hotp_secret', true);

		if (!$hotp_secret) {
			return false;
		}

		$original_hotp_secret = $is_encryption_enabled
			? EncryptionService::decrypt($hotp_secret)
			: $hotp_secret;

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

		// Get current counter
		$hotp_counter = get_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', true);
		if ($hotp_counter === false || $hotp_counter === '') {
			$hotp_counter = 0;
		}
		$hotp_counter = (int)$hotp_counter;

		// Create HOTP object and generate code at CURRENT counter
		$hotp = HOTP::create($original_hotp_secret);
		$hotp_code = $hotp->at($hotp_counter);

		// Increment counter for next use
		$new_counter = $hotp_counter + 1;
		update_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', $new_counter);

		return [
			'hotp_code' => esc_html($hotp_code),
			'hotp_counter' => $new_counter,
		];
	}

	/**
	 * Verify HOTP code with counter window (fixes synchronization issues)
	 *
	 * @param int $user_id User ID
	 * @param int $entered_code Entered code
	 * @param int $window Counter window for verification
	 * @return bool True if valid
	 */
	public static function verify_code_with_window($user_id, $entered_code, $window = 10)
	{
		$user = get_userdata($user_id);
		if ($user === false) {
			return false;
		}

		$is_encryption_enabled = EncryptionService::is_encryption_enabled();
		$hotp_secret = get_user_meta($user->ID, 'ultimate_security_2fa_hotp_secret', true);

		if (!$hotp_secret) {
			return false;
		}

		$original_hotp_secret = $is_encryption_enabled ? EncryptionService::decrypt($hotp_secret) : $hotp_secret;

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

		$hotp = HOTP::create($original_hotp_secret);
		$counter = get_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', true);

		// Ensure counter is at least 0
		if ($counter === false || $counter === '') {
			$counter = 0;
			update_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', 0);
		}

		// Convert counter to int
		$counter = intval($counter);

		// Sanitize the entered code - remove any spaces and ensure it's a string
		$entered_code = preg_replace('/\s+/', '', (string)$entered_code);

		// Try current counter and window ahead (HOTP only moves forward)
		for ($i = 0; $i <= $window; $i++) {
			$test_counter = $counter + $i;
			$test_code = $hotp->at($test_counter);

			// Use timing-safe comparison
			if (hash_equals((string)$test_code, (string)$entered_code)) {
				// Update counter to next value after the matched one
				update_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', $test_counter + 1);
				return true;
			}
		}

		return false;
	}

	/**
	 * Reset HOTP secret and counter for user
	 *
	 * @param int $user_id User ID
	 * @return array|false New OTP data or false
	 */
	public static function reset_secret($user_id)
	{
		delete_user_meta($user_id, 'ultimate_security_2fa_hotp_secret');
		delete_user_meta($user_id, 'ultimate_security_2fa_hotp_counter');
		return self::get_otp_data($user_id);
	}

	/**
	 * Check if HOTP is configured for user
	 *
	 * @param int $user_id User ID
	 * @return bool True if configured
	 */
	public static function is_configured($user_id)
	{
		$hotp_secret = get_user_meta($user_id, 'ultimate_security_2fa_hotp_secret', true);
		$hotp_counter = get_user_meta($user_id, 'ultimate_security_2fa_hotp_counter', true);

		// Counter can be 0, so we need to check if it's set (not just !empty)
		$has_counter = ($hotp_counter !== false && $hotp_counter !== '');

		return !empty($hotp_secret) && $has_counter;
	}

	/**
	 * Disable HOTP for user
	 *
	 * @param int $user_id User ID
	 * @return bool Success status
	 */
	public static function disable_for_user($user_id)
	{
		delete_user_meta($user_id, 'ultimate_security_2fa_hotp_secret');
		delete_user_meta($user_id, 'ultimate_security_2fa_hotp_counter');
		return true;
	}
}
