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

use WPUltimateSecurity\UltimateSecurity\Modules\Authentication\TwoFactor\Helpers\DeviceFingerprint;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\Utils;
use WPUltimateSecurity\UltimateSecurity\Support\Helpers\DateHelper;

/**
 * Rate Limiting Service for 2FA
 *
 * Prevents brute force attacks on 2FA verification.
 * Tracks both user-based and IP-based attempts.
 * Settings are configurable via the admin panel.
 */
class RateLimitService
{
	/**
	 * Default max attempts per user (fallback if not configured)
	 */
	const DEFAULT_MAX_ATTEMPTS = 5;

	/**
	 * Default lockout duration in seconds (fallback if not configured)
	 */
	const DEFAULT_LOCKOUT_DURATION = 900; // 15 minutes

	/**
	 * Default max attempts per IP (global threshold)
	 */
	const DEFAULT_IP_MAX_ATTEMPTS = 50;

	/**
	 * IP tracking window in seconds (1 hour)
	 */
	const IP_TRACKING_WINDOW = 3600;

	/**
	 * Get max attempts from settings
	 *
	 * @return int
	 */
	private static function get_max_attempts()
	{
		$options = Utils::ultimate_security_get_option();
		$max = isset($options['security']['2fa_authentication']['rate_limiting']['max_attempts'])
			? absint($options['security']['2fa_authentication']['rate_limiting']['max_attempts'])
			: self::DEFAULT_MAX_ATTEMPTS;

		// Enforce reasonable bounds (3-10)
		return max(3, min(10, $max));
	}

	/**
	 * Get lockout duration from settings
	 *
	 * @return int Duration in seconds
	 */
	private static function get_lockout_duration()
	{
		$options = Utils::ultimate_security_get_option();
		$minutes = isset($options['security']['2fa_authentication']['rate_limiting']['lockout_duration'])
			? absint($options['security']['2fa_authentication']['rate_limiting']['lockout_duration'])
			: (self::DEFAULT_LOCKOUT_DURATION / 60);

		// Enforce reasonable bounds (5-60 minutes)
		$minutes = max(5, min(60, $minutes));

		return $minutes * 60; // Convert to seconds
	}

	/**
	 * Check rate limit for 2FA attempts
	 *
	 * @param int $user_id User ID
	 * @return array Rate limit status with 'allowed', 'attempts', 'remaining', 'message'
	 */
	public static function check_rate_limit($user_id)
	{
		$max_attempts = self::get_max_attempts();
		$lockout_duration = self::get_lockout_duration();

		$attempts_key = 'ultimate_security_2fa_attempts_' . $user_id;
		$lockout_key = 'ultimate_security_2fa_lockout_' . $user_id;

		// Check if locked out
		if (get_transient($lockout_key)) {
			$lockout_time = get_transient($lockout_key);
			return [
				'allowed' => false,
				'lockout_until' => $lockout_time,
				'message' => sprintf(
					__('Too many failed attempts. Please try again in %d minutes.', 'ultimate-security'),
					ceil(($lockout_time - time()) / 60)
				)
			];
		}

		// Get current attempts
		$attempts = get_transient($attempts_key);
		if ($attempts === false) {
			$attempts = 0;
		}

		if ($attempts >= $max_attempts) {
			// Lock out user
			$lockout_until = time() + $lockout_duration;
			set_transient($lockout_key, $lockout_until, $lockout_duration);
			delete_transient($attempts_key);
			$options = Utils::ultimate_security_get_option();
			if (isset($options['security']['2fa_authentication']['audit_logs']['enable']) && $options['security']['2fa_authentication']['audit_logs']['enable']) {
				if (class_exists('\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository')) {
					// Log lockout event
					\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository::log_event($user_id, '2fa_lockout', [
						'attempts' => $attempts,
						'lockout_duration' => $lockout_duration,
						'timestamp' => DateHelper::now()
					]);
				}
			}

			// Fire lockout action for SimpleEventLogger
			do_action('ultimate_security_user_locked_out', $user_id, __('Too many failed 2FA attempts', 'ultimate-security'), $lockout_duration);

			return [
				'allowed' => false,
				'lockout_until' => $lockout_until,
				'message' => sprintf(
					__('Too many failed attempts. Locked out for %d minutes.', 'ultimate-security'),
					$lockout_duration / 60
				)
			];
		}

		return [
			'allowed' => true,
			'attempts' => $attempts,
			'remaining' => $max_attempts - $attempts
		];
	}

	/**
	 * Record failed 2FA attempt
	 *
	 * @param int $user_id User ID
	 * @return int Current attempt count
	 */
	public static function record_failed_attempt($user_id)
	{
		$attempts_key = 'ultimate_security_2fa_attempts_' . $user_id;
		$attempts = get_transient($attempts_key);
		if ($attempts === false) {
			$attempts = 0;
		}
		$attempts++;
		set_transient($attempts_key, $attempts, 30 * MINUTE_IN_SECONDS);

		$options = Utils::ultimate_security_get_option();
		if (isset($options['security']['2fa_authentication']['audit_logs']['enable']) && $options['security']['2fa_authentication']['audit_logs']['enable']) {
			// Log failed attempt
			if (class_exists('\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository')) {
				\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository::log_event($user_id, '2fa_verification_failed', [
					'attempt_number' => $attempts,
					'ip_address' => DeviceFingerprint::get_client_ip(),
					'timestamp' => DateHelper::now()
				]);
			}
		}

		// Fire 2FA failed action for SimpleEventLogger
		do_action('ultimate_security_2fa_failed', $user_id, 'rate_limited');

		return $attempts;
	}

	/**
	 * Clear rate limiting on successful verification
	 *
	 * @param int $user_id User ID
	 */
	public static function clear_rate_limit($user_id)
	{
		$attempts_key = 'ultimate_security_2fa_attempts_' . $user_id;
		$lockout_key = 'ultimate_security_2fa_lockout_' . $user_id;
		delete_transient($attempts_key);
		delete_transient($lockout_key);
	}

	/**
	 * Get max attempts per IP from settings
	 *
	 * @return int
	 */
	private static function get_ip_max_attempts()
	{
		$options = Utils::ultimate_security_get_option();
		$max = isset($options['security']['2fa_authentication']['rate_limiting']['ip_max_attempts'])
			? absint($options['security']['2fa_authentication']['rate_limiting']['ip_max_attempts'])
			: self::DEFAULT_IP_MAX_ATTEMPTS;

		// Enforce reasonable bounds (10-100)
		return max(10, min(100, $max));
	}

	/**
	 * Check if IP is blocked due to too many attempts
	 *
	 * @param string|null $ip IP address (auto-detected if null)
	 * @return array Rate limit status with 'blocked', 'attempts', 'message'
	 */
	public static function check_ip_rate_limit($ip = null)
	{
		if ($ip === null) {
			$ip = DeviceFingerprint::get_client_ip();
		}

		$max_attempts = self::get_ip_max_attempts();
		$ip_hash = md5($ip); // Hash for storage
		$lockout_key = 'ultimate_security_2fa_ip_lockout_' . $ip_hash;
		$attempts_key = 'ultimate_security_2fa_ip_attempts_' . $ip_hash;

		// Check if IP is locked out
		$lockout_time = get_transient($lockout_key);
		if ($lockout_time) {
			$remaining = $lockout_time - time();
			if ($remaining > 0) {
				return [
					'blocked' => true,
					'lockout_until' => $lockout_time,
					'message' => sprintf(
						__('Too many requests from your IP address. Please try again in %d minutes.', 'ultimate-security'),
						ceil($remaining / 60)
					)
				];
			}
		}

		// Get current attempts
		$attempts = get_transient($attempts_key);
		if ($attempts === false) {
			$attempts = 0;
		}

		if ($attempts >= $max_attempts) {
			// Lock out IP for 1 hour
			$lockout_until = time() + self::IP_TRACKING_WINDOW;
			set_transient($lockout_key, $lockout_until, self::IP_TRACKING_WINDOW);
			delete_transient($attempts_key);

			// Log IP lockout
			$options = Utils::ultimate_security_get_option();
			if (isset($options['security']['2fa_authentication']['audit_logs']['enable']) && $options['security']['2fa_authentication']['audit_logs']['enable']) {
				if (class_exists('\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository')) {
					\WPUltimateSecurity\UltimateSecurityPro\Modules\Authentication\TwoFactor\Storage\LogRepository::log_event(0, '2fa_ip_lockout', [
						'ip_address' => $ip,
						'attempts' => $attempts,
						'timestamp' => DateHelper::now()
					]);
				}
			}

			return [
				'blocked' => true,
				'lockout_until' => $lockout_until,
				'message' => __('Too many requests from your IP address. Please try again in 1 hour.', 'ultimate-security')
			];
		}

		return [
			'blocked' => false,
			'attempts' => $attempts,
			'remaining' => $max_attempts - $attempts
		];
	}

	/**
	 * Record failed attempt for IP address
	 *
	 * @param string|null $ip IP address (auto-detected if null)
	 * @return int Current attempt count for this IP
	 */
	public static function record_ip_failed_attempt($ip = null)
	{
		if ($ip === null) {
			$ip = DeviceFingerprint::get_client_ip();
		}

		$ip_hash = md5($ip);
		$attempts_key = 'ultimate_security_2fa_ip_attempts_' . $ip_hash;

		$attempts = get_transient($attempts_key);
		if ($attempts === false) {
			$attempts = 0;
		}
		$attempts++;

		// Track for 1 hour window
		set_transient($attempts_key, $attempts, self::IP_TRACKING_WINDOW);

		return $attempts;
	}

	/**
	 * Check both user and IP rate limits
	 *
	 * @param int $user_id User ID
	 * @return array Combined rate limit status
	 */
	public static function check_combined_rate_limit($user_id)
	{
		// Check IP first (broader protection)
		$ip_check = self::check_ip_rate_limit();
		if ($ip_check['blocked']) {
			return [
				'allowed' => false,
				'reason' => 'ip',
				'message' => $ip_check['message']
			];
		}

		// Check user-specific limit
		$user_check = self::check_rate_limit($user_id);
		if (!$user_check['allowed']) {
			return [
				'allowed' => false,
				'reason' => 'user',
				'message' => $user_check['message']
			];
		}

		return [
			'allowed' => true,
			'user_remaining' => $user_check['remaining'],
			'ip_remaining' => $ip_check['remaining']
		];
	}

	/**
	 * Record failed attempt for both user and IP
	 *
	 * @param int $user_id User ID
	 * @return array Attempt counts for user and IP
	 */
	public static function record_combined_failed_attempt($user_id)
	{
		$user_attempts = self::record_failed_attempt($user_id);
		$ip_attempts = self::record_ip_failed_attempt();

		return [
			'user_attempts' => $user_attempts,
			'ip_attempts' => $ip_attempts
		];
	}

	/**
	 * Check if a specific IP is currently blocked
	 *
	 * @param string $ip IP address to check
	 * @return bool True if blocked
	 */
	public static function is_ip_blocked($ip)
	{
		$result = self::check_ip_rate_limit($ip);
		return $result['blocked'];
	}
}
