<?php
/**
 * Security Handler — manages CAPTCHA, Brute Force, and rate limiting.
 *
 * @package PrivexPasswordProtect
 */

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

/**
 * Class PPMSTR_Security_Handler
 */
class PPMSTR_Security_Handler {

	/**
	 * Verify CAPTCHA submission if enabled.
	 * PRO feature — always returns true (pass) in the free version.
	 *
	 * @return true|string True if valid, string error message if invalid.
	 */
	public static function verify_captcha() {
		/* fs_start_pro */
		if ( function_exists( 'ppp_fs' ) && ppp_fs()->can_use_premium_code__premium_only() ) {
			$settings = get_option( 'ppmstr_settings', array() );
			$provider = $settings['captcha_provider'] ?? 'none';
			$secret   = $settings['captcha_secret_key'] ?? '';

			if ( 'none' === $provider || empty( $secret ) ) {
				return true;
			}

			if ( in_array( $provider, array( 'recaptcha_v2', 'recaptcha_v3' ), true ) ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing
				$response = isset( $_POST['g-recaptcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['g-recaptcha-response'] ) ) : '';
				if ( empty( $response ) ) {
					return __( 'Please complete the CAPTCHA.', 'privex-password-protect' );
				}
				$verify = wp_remote_post(
					'https://www.google.com/recaptcha/api/siteverify',
					array(
						'body' => array(
							'secret'   => $secret,
							'response' => $response,
							'remoteip' => self::get_ip(),
						),
					)
				);
				if ( ! is_wp_error( $verify ) ) {
					$body = json_decode( wp_remote_retrieve_body( $verify ), true );
					if ( ! empty( $body['success'] ) ) {
						return true;
					}
				}
				return __( 'CAPTCHA verification failed.', 'privex-password-protect' );
			}

			if ( 'hcaptcha' === $provider ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing
				$response = isset( $_POST['h-captcha-response'] ) ? sanitize_text_field( wp_unslash( $_POST['h-captcha-response'] ) ) : '';
				if ( empty( $response ) ) {
					return __( 'Please complete the CAPTCHA.', 'privex-password-protect' );
				}
				$verify = wp_remote_post(
					'https://hcaptcha.com/siteverify',
					array(
						'body' => array(
							'secret'   => $secret,
							'response' => $response,
						),
					)
				);
				if ( ! is_wp_error( $verify ) ) {
					$body = json_decode( wp_remote_retrieve_body( $verify ), true );
					if ( ! empty( $body['success'] ) ) {
						return true;
					}
				}
				return __( 'CAPTCHA verification failed.', 'privex-password-protect' );
			}

			if ( 'turnstile' === $provider ) {
				// phpcs:ignore WordPress.Security.NonceVerification.Missing
				$response = isset( $_POST['cf-turnstile-response'] ) ? sanitize_text_field( wp_unslash( $_POST['cf-turnstile-response'] ) ) : '';
				if ( empty( $response ) ) {
					return __( 'Please complete the CAPTCHA.', 'privex-password-protect' );
				}
				$verify = wp_remote_post(
					'https://challenges.cloudflare.com/turnstile/v0/siteverify',
					array(
						'body' => array(
							'secret'   => $secret,
							'response' => $response,
							'remoteip' => self::get_ip(),
						),
					)
				);
				if ( ! is_wp_error( $verify ) ) {
					$body = json_decode( wp_remote_retrieve_body( $verify ), true );
					if ( ! empty( $body['success'] ) ) {
						return true;
					}
				}
				return __( 'CAPTCHA verification failed.', 'privex-password-protect' );
			}

			return true;
		} // End PRO check.
		/* fs_end_pro */

		return true; // Free version: CAPTCHA always passes.
	}

	/**
	 * Check if current IP is locked out.
	 *
	 * @return true|string True if allowed, string error if locked out.
	 */
	public static function check_brute_force() {
		$settings = get_option( 'ppmstr_settings', array() );
		if ( empty( $settings['limit_login_enabled'] ) ) {
			return true;
		}

		$ip      = self::get_ip();
		$lockout = absint( $settings['limit_login_lockout'] ?? 15 );

		$transient_key = 'ppmstr_bf_' . md5( $ip );
		$data          = get_transient( $transient_key );

		if ( $data && is_array( $data ) ) {
			if ( ! empty( $data['locked'] ) && $data['locked'] > time() ) {
				$remaining = ceil( ( $data['locked'] - time() ) / 60 );
				/* translators: %d: minutes */
				return sprintf( __( 'Too many failed attempts. Please try again in %d minute(s).', 'privex-password-protect' ), $remaining );
			}
		}

		return true;
	}

	/**
	 * Record a failed attempt for Brute Force tracking.
	 */
	public static function record_failed_attempt() {
		$settings = get_option( 'ppmstr_settings', array() );
		if ( empty( $settings['limit_login_enabled'] ) ) {
			return;
		}

		$ip      = self::get_ip();
		$max     = absint( $settings['limit_login_max'] ?? 5 );
		$lockout = absint( $settings['limit_login_lockout'] ?? 15 ); // minutes.

		$transient_key = 'ppmstr_bf_' . md5( $ip );
		$data          = get_transient( $transient_key );

		if ( ! $data || ! is_array( $data ) ) {
			$data = array( 'count' => 0, 'locked' => 0 );
		}

		$data['count']++;

		if ( $data['count'] >= $max ) {
			$data['locked'] = time() + ( $lockout * 60 );
			$data['count']  = 0; // Reset count after locking.
			set_transient( $transient_key, $data, $lockout * 60 );
		} else {
			// Keep tally for an hour.
			set_transient( $transient_key, $data, 3600 );
		}
	}

	/**
	 * Reset failed attempts on success.
	 */
	public static function reset_failed_attempts() {
		$ip = self::get_ip();
		delete_transient( 'ppmstr_bf_' . md5( $ip ) );
	}

	/**
	 * Get visitor IP address.
	 *
	 * @return string IP address.
	 */
	public static function get_ip() {
		$ip      = '127.0.0.1';
		$headers = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' );
		foreach ( $headers as $hdr ) {
			if ( ! empty( $_SERVER[ $hdr ] ) ) {
				$raw = sanitize_text_field( wp_unslash( $_SERVER[ $hdr ] ) );
				if ( strpos( $raw, ',' ) !== false ) {
					$raw = trim( explode( ',', $raw )[0] );
				}
				if ( filter_var( $raw, FILTER_VALIDATE_IP ) ) {
					$ip = $raw;
					break;
				}
			}
		}
		return $ip;
	}

	/**
	 * Get CAPTCHA HTML and enqueue needed scripts.
	 * PRO feature — returns empty string in the free version.
	 *
	 * @return string
	 */
	public static function get_captcha_html() {
		/* fs_start_pro */
		if ( function_exists( 'ppp_fs' ) && ppp_fs()->can_use_premium_code__premium_only() ) {
			$settings = get_option( 'ppmstr_settings', array() );
			$provider = $settings['captcha_provider'] ?? 'none';
			$site_key = $settings['captcha_site_key'] ?? '';

			if ( 'none' === $provider || empty( $site_key ) ) {
				return '';
			}

			$html = '';
			if ( 'recaptcha_v2' === $provider ) {
				wp_enqueue_script( 'ppmstr-recaptcha', 'https://www.google.com/recaptcha/api.js', array(), PPMSTR_VERSION, true );
				$html = '<div class="g-recaptcha" data-sitekey="' . esc_attr( $site_key ) . '" style="margin-bottom:15px;display:flex;justify-content:center;"></div>';
			} elseif ( 'hcaptcha' === $provider ) {
				wp_enqueue_script( 'ppmstr-hcaptcha', 'https://js.hcaptcha.com/1/api.js', array(), PPMSTR_VERSION, true );
				$html = '<div class="h-captcha" data-sitekey="' . esc_attr( $site_key ) . '" style="margin-bottom:15px;display:flex;justify-content:center;"></div>';
			} elseif ( 'turnstile' === $provider ) {
				// phpcs:ignore PluginCheck.CodeAnalysis.EnqueuedResourceOffloading.OffloadedContent
				wp_enqueue_script( 'ppmstr-turnstile', 'https://challenges.cloudflare.com/turnstile/v0/api.js', array(), PPMSTR_VERSION, true );
				$html = '<div class="cf-turnstile" data-sitekey="' . esc_attr( $site_key ) . '" style="margin-bottom:15px;display:flex;justify-content:center;"></div>';
			} elseif ( 'recaptcha_v3' === $provider ) {
				wp_enqueue_script( 'ppmstr-recaptcha-v3', 'https://www.google.com/recaptcha/api.js?render=' . esc_attr( $site_key ), array(), PPMSTR_VERSION, true );
				$v3_js = "document.addEventListener('DOMContentLoaded',function(){grecaptcha.ready(function(){grecaptcha.execute('" . esc_js( $site_key ) . "',{action:'submit'}).then(function(token){document.querySelectorAll('.ppmstr-v3-token').forEach(function(el){el.value=token;});});});});";
				wp_add_inline_script( 'ppmstr-recaptcha-v3', $v3_js );
				$html = '<input type="hidden" name="g-recaptcha-response" id="g-recaptcha-response-' . wp_rand( 1000, 9999 ) . '" class="ppmstr-v3-token" />';
			}

			return $html;
		} // End PRO check.
		/* fs_end_pro */

		return ''; // Free version: no CAPTCHA HTML.
	}

	/**
	 * Register a new active session for concurrent tracking.
	 * PRO feature — does nothing in the free version.
	 */
	public static function register_session() {
		/* fs_start_pro */
		if ( ! function_exists( 'ppp_fs' ) || ! ppp_fs()->can_use_premium_code__premium_only() ) {
			return;
		}

		$settings = get_option( 'ppmstr_settings', array() );
		if ( empty( $settings['concurrent_logins_enabled'] ) ) {
			return;
		}

		$max  = absint( $settings['concurrent_logins_max'] ?? 3 );
		$sess = wp_generate_password( 24, false );

		// Set the session cookie.
		setcookie( 'ppmstr_session', $sess, time() + YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN );
		$_COOKIE['ppmstr_session'] = $sess; // Make available immediately.

		$active = get_option( 'ppmstr_active_sessions', array() );
		if ( ! is_array( $active ) ) {
			$active = array();
		}

		// Add new session.
		$active[] = array( 'id' => $sess, 'time' => time() );

		// Trim to max allowed.
		if ( count( $active ) > $max ) {
			usort( $active, function ( $a, $b ) {
				return $a['time'] <=> $b['time'];
			} );
			// Keep only the newest $max sessions.
			$active = array_slice( $active, -$max );
		}

		update_option( 'ppmstr_active_sessions', $active );
		/* fs_end_pro */
	}

	/**
	 * Verify if current browser's session is still active (not kicked).
	 * PRO feature — always returns true in the free version.
	 *
	 * @return bool True if valid or tracking disabled, False if session was kicked.
	 */
	public static function is_valid_session() {
		/* fs_start_pro */
		if ( function_exists( 'ppp_fs' ) && ppp_fs()->can_use_premium_code__premium_only() ) {
			$settings = get_option( 'ppmstr_settings', array() );
			if ( empty( $settings['concurrent_logins_enabled'] ) ) {
				return true;
			}

			$sess = isset( $_COOKIE['ppmstr_session'] ) ? sanitize_text_field( wp_unslash( $_COOKIE['ppmstr_session'] ) ) : '';
			if ( empty( $sess ) ) {
				return false; // No session cookie found.
			}

			$active = get_option( 'ppmstr_active_sessions', array() );
			if ( ! is_array( $active ) ) {
				return true;
			}

			foreach ( $active as $a ) {
				if ( $a['id'] === $sess ) {
					return true;
				}
			}

			return false; // Kicked.
		} // End PRO check.
		/* fs_end_pro */

		return true; // Free version: sessions always valid.
	}
}
