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

use WPUltimateSecurity\UltimateSecurity\Support\Exceptions\AuthenticationException;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\CryptoHelper;

/**
 * Token Service for 2FA Authentication
 *
 * Provides token-based authentication similar to popular 2FA plugins
 * (Two Factor by WordPress.org, WP 2FA by Melapress)
 *
 * Instead of cookies, uses hidden form fields with secure tokens:
 * - wp-auth-id: User ID
 * - wp-auth-nonce: Secure authentication token
 * - provider: 2FA provider type
 */
class TokenService
{
	/**
	 * Token expiration time in seconds (30 minutes)
	 */
	const TOKEN_EXPIRATION = 1800;

	/**
	 * User meta key for storing the authentication token
	 */
	const META_AUTH_NONCE = '_ultimate_security_2fa_auth_nonce';

	/**
	 * User meta key for storing token expiration
	 */
	const META_AUTH_NONCE_EXPIRES = '_ultimate_security_2fa_auth_nonce_expires';

	/**
	 * Generate a secure authentication token for 2FA verification
	 *
	 * @param int $user_id User ID
	 * @return string The generated authentication nonce (64 character hex string)
	 */
	public static function generate_auth_token($user_id)
	{
		// Generate a cryptographically secure random token using CryptoHelper
		$token = CryptoHelper::generateToken(32, 'hex'); // 64 character hex string

		// Hash the token before storing (like Two Factor plugin does)
		$hashed_token = self::hash_token($token, $user_id);

		// Store the hashed token and expiration in user meta
		update_user_meta($user_id, self::META_AUTH_NONCE, $hashed_token);
		update_user_meta($user_id, self::META_AUTH_NONCE_EXPIRES, time() + self::TOKEN_EXPIRATION);

		return $token;
	}

	/**
	 * Validate an authentication token
	 *
	 * @param int    $user_id The user ID
	 * @param string $token   The token to validate
	 * @return bool True if token is valid, false otherwise
	 *
	 * @throws AuthenticationException When token is invalid or expired.
	 */
	public static function validate_auth_token($user_id, $token)
	{
		if (empty($user_id) || empty($token)) {
			throw AuthenticationException::tokenInvalid('2FA');
		}

		// Get stored token hash and expiration
		$stored_hash = get_user_meta($user_id, self::META_AUTH_NONCE, true);
		$expires = get_user_meta($user_id, self::META_AUTH_NONCE_EXPIRES, true);

		// Check if token exists
		if (empty($stored_hash)) {
			throw AuthenticationException::tokenInvalid('2FA');
		}

		// Check if token has expired
		if (empty($expires) || time() > (int) $expires) {
			self::clear_auth_token($user_id);
			throw AuthenticationException::tokenExpired('2FA');
		}

		// Hash the provided token and compare
		$token_hash = self::hash_token($token, $user_id);

		// Use timing-safe comparison via CryptoHelper to prevent timing attacks
		if (!CryptoHelper::compare($stored_hash, $token_hash)) {
			throw AuthenticationException::tokenInvalid('2FA');
		}

		return true;
	}

	/**
	 * Clear the authentication token after successful verification or expiration
	 *
	 * @param int $user_id User ID
	 */
	public static function clear_auth_token($user_id)
	{
		delete_user_meta($user_id, self::META_AUTH_NONCE);
		delete_user_meta($user_id, self::META_AUTH_NONCE_EXPIRES);
	}

	/**
	 * Check if a user has a pending 2FA token
	 *
	 * @param int $user_id User ID
	 * @return bool True if user has a valid pending token
	 */
	public static function has_pending_token($user_id)
	{
		$stored_hash = get_user_meta($user_id, self::META_AUTH_NONCE, true);
		$expires = get_user_meta($user_id, self::META_AUTH_NONCE_EXPIRES, true);

		if (empty($stored_hash)) {
			return false;
		}

		// Check if expired
		if (empty($expires) || time() > (int) $expires) {
			self::clear_auth_token($user_id);
			return false;
		}

		return true;
	}

	/**
	 * Get token expiration time remaining in seconds
	 *
	 * @param int $user_id User ID
	 * @return int Seconds remaining, 0 if expired
	 */
	public static function get_token_time_remaining($user_id)
	{
		$expires = get_user_meta($user_id, self::META_AUTH_NONCE_EXPIRES, true);

		if (empty($expires)) {
			return 0;
		}

		$remaining = (int) $expires - time();
		return max(0, $remaining);
	}

	/**
	 * Hash a token for secure storage using CryptoHelper
	 * Uses HMAC with user-specific salt for additional security
	 *
	 * @param string $token   The token to hash
	 * @param int    $user_id The user ID (used as part of the salt)
	 * @return string The hashed token
	 */
	private static function hash_token($token, $user_id)
	{
		// Use WordPress auth salt + user ID for uniqueness
		$salt = wp_salt('auth') . '|' . $user_id;

		return CryptoHelper::hmac($token, $salt);
	}

	/**
	 * Generate hidden form fields for the 2FA verification form
	 *
	 * @param int    $user_id  User ID
	 * @param string $provider 2FA provider (email, totp, hotp, etc.)
	 * @param string $token    Authentication token
	 * @return string HTML hidden input fields
	 */
	public static function get_hidden_fields($user_id, $provider, $token)
	{
		$html = '<input type="hidden" name="provider" id="provider" value="' . esc_attr($provider) . '" />';
		$html .= '<input type="hidden" name="wp-auth-id" id="wp-auth-id" value="' . esc_attr($user_id) . '" />';
		$html .= '<input type="hidden" name="wp-auth-nonce" id="wp-auth-nonce" value="' . esc_attr($token) . '" />';

		return $html;
	}

	/**
	 * Get authentication data from POST request
	 *
	 * @return array|false Array with user_id, token, provider or false if missing
	 */
	public static function get_auth_data_from_request()
	{
		if (!isset($_POST['wp-auth-id']) || !isset($_POST['wp-auth-nonce'])) {
			return false;
		}

		$user_id = absint(sanitize_text_field(wp_unslash($_POST['wp-auth-id'])));
		$token = sanitize_text_field(wp_unslash($_POST['wp-auth-nonce']));
		$provider = isset($_POST['provider']) ? sanitize_text_field(wp_unslash($_POST['provider'])) : '';

		if (empty($user_id) || empty($token)) {
			return false;
		}

		return [
			'user_id' => $user_id,
			'token' => $token,
			'provider' => $provider,
		];
	}

	/**
	 * Validate authentication from POST request
	 *
	 * @return array|false Array with user_id, provider on success, false on failure
	 *
	 * @throws AuthenticationException When validation fails.
	 */
	public static function validate_auth_from_request()
	{
		$auth_data = self::get_auth_data_from_request();

		if (!$auth_data) {
			throw AuthenticationException::tokenInvalid('2FA');
		}

		// Validate the token (will throw AuthenticationException if invalid)
		self::validate_auth_token($auth_data['user_id'], $auth_data['token']);

		// Check that user exists and has 2FA pending
		$user = get_user_by('ID', $auth_data['user_id']);
		if (!$user) {
			throw AuthenticationException::invalidCredentials();
		}

		// Check pending flag
		$pending = get_user_meta($auth_data['user_id'], 'ultimate_security_2fa_pending', true);
		if (!$pending) {
			throw AuthenticationException::twoFactorInvalid($auth_data['user_id']);
		}

		return [
			'user_id' => $auth_data['user_id'],
			'user' => $user,
			'provider' => $auth_data['provider'],
		];
	}
}
