<?php
namespace SabaiApps\Framework\Application;

/**
 * HttpRequest class for handling HTTP request data.
 * Provides methods for accessing request parameters, cookies, headers, files, and other HTTP-specific information.
 */
class HttpRequest extends AbstractRequest {

	/**
	 * Cached cookie data.
	 *
	 * @var array
	 */
	protected static $_cookie;

	/**
	 * Constructor.
	 *
	 * Initializes request parameters from global variables ($_GET, $_POST) and handles filtering and slashes removal.
	 *
	 * @param bool       $filterGlobals Whether to filter global variables for security.
	 * @param bool       $forceStripSlashes Whether to forcibly strip slashes from input.
	 * @param array|null $params Optional custom parameters to use instead of globals.
	 */
	public function __construct( $filterGlobals = true, $forceStripSlashes = false, ?array $params = null ) {
		if ( ! isset( $params ) ) {
			switch ( strtolower( self::method() ) ) {
				case 'get':
					$params = $_GET;
					break;
				default:
					$params = array_merge( $_GET, $_POST );
			}
		}

		if ( ! isset( self::$_cookie ) ) {
			$cookie = $_COOKIE;
		}

		if ( $filterGlobals ) {
			if ( $forceStripSlashes ) {
				$params = self::_stripSlashes( $params );
				if ( ! empty( $cookie ) ) {
					$cookie = self::_stripSlashes( $cookie );
				}
			}
			// Filter malicious user inputs
			$list = array( 'GLOBALS', '_GET', '_POST', '_REQUEST', '_COOKIE', '_ENV', '_FILES', '_SERVER', '_SESSION' );
			self::_filterUserData( $params, $list );
			if ( ! empty( $cookie ) ) {
				self::_filterUserData( $cookie, $list );
			}
		}

		parent::__construct( $params );
		if ( ! isset( self::$_cookie ) ) {
			self::$_cookie = $cookie;
		}
	}

	/**
	 * Recursively strips slashes from input variables.
	 *
	 * @param mixed $var Input variable.
	 * @return mixed
	 */
	protected static function _stripSlashes( $var ) {
		if ( is_array( $var ) ) {
			return array_map( array( __CLASS__, __FUNCTION__ ), $var );
		}

		return is_string( $var ) ? stripslashes( $var ) : $var;
	}

	/**
	 * Recursively filters user data to prevent injection of global variables.
	 *
	 * @param mixed $var Input variable (by reference).
	 * @param array $globalKeys List of global variable names to filter.
	 */
	protected static function _filterUserData( &$var, $globalKeys = array() ) {
		if ( is_array( $var ) ) {
			$var_keys = array_keys( $var );
			if ( array_intersect( $globalKeys, $var_keys ) ) {
				$var = array();
			} else {
				foreach ( $var_keys as $key ) {
					self::_filterUserData( $var[ $key ], $globalKeys );
				}
			}
		} elseif ( is_string( $var ) ) {
				$var = str_replace( "\x00", '', $var );
		}
	}

	/**
	 * Gets a cookie value by name.
	 *
	 * @param string $name Cookie name.
	 * @return mixed|null
	 */
	public static function cookie( $name ) {
		return isset( self::$_cookie[ $name ] ) ? self::$_cookie[ $name ] : null;
	}

	/**
	 * Gets a request header value by name.
	 *
	 * @param string $name Header name.
	 * @return mixed|null
	 */
	public static function header( $name ) {
		$php_name = 'HTTP_' . strtoupper( $name );

		return isset( $_SERVER[ $php_name ] ) ? $_SERVER[ $php_name ] : null;
	}

	/**
	 * Gets the HTTP request method.
	 *
	 * @return string
	 */
	public static function method() {
		return $_SERVER['REQUEST_METHOD'];
	}

	/**
	 * Checks if the request method is POST.
	 *
	 * @return bool
	 */
	public static function isPostMethod() {
		return strcasecmp( self::method(), 'POST' ) === 0;
	}

	/**
	 * Gets the full request URL.
	 *
	 * @param bool $withQueryStr Whether to include the query string.
	 * @return string
	 */
	public static function url( $withQueryStr = true ) {
		$url = sprintf(
			'%s://%s%s',
			! empty( $_SERVER['HTTPS'] ) && 'off' !== $_SERVER['HTTPS'] ? 'https' : 'http',
			! empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : 'localhost',
			self::uri()
		);
		if ( ! $withQueryStr ) {
			if ( $pos = strpos( $url, '?' ) ) {
				$url = substr( $url, 0, $pos );
			}
		}
		return $url;
	}

	/**
	 * Gets the request URI.
	 *
	 * @return string
	 */
	public static function uri() {
		if ( empty( $_SERVER['PHP_SELF'] ) || empty( $_SERVER['REQUEST_URI'] ) ) {
			// IIS compatibility
			$_SERVER['REQUEST_URI'] = $_SERVER['SCRIPT_NAME'];

			if ( ! empty( $_SERVER['QUERY_STRING'] ) ) {
				$_SERVER['REQUEST_URI'] .= '?' . $_SERVER['QUERY_STRING'];
			}
		}

		return $_SERVER['REQUEST_URI'];
	}

	/**
	 * Checks if the request is an XMLHttpRequest (AJAX).
	 *
	 * @return bool
	 */
	public static function isXhr() {
		return ( isset( $_SERVER['HTTP_X_REQUESTED_WITH'] )
			&& strcasecmp( $_SERVER['HTTP_X_REQUESTED_WITH'], 'XMLHttpRequest' ) === 0 );
	}

	/**
	 * Gets uploaded file information by name.
	 *
	 * @param string $name Name of the file input.
	 * @return array
	 */
	public static function files( $name ) {
		if ( empty( $_FILES ) ) {
			return array();
		}

		if ( isset( $_FILES[ $name ] ) ) {
			return $_FILES[ $name ];
		}

		if ( false === $pos = strpos( $name, '[' ) ) {
			return array();
		}

		$base   = substr( $name, 0, $pos );
		$key    = str_replace( array( ']', '[' ), array( '', '"]["' ), substr( $name, $pos + 1, -1 ) );
		$code   = array( sprintf( 'if (!isset($_FILES["%s"]["name"]["%s"])) return [];', $base, $key ) );
		$code[] = '$file = [];';
		foreach ( array( 'name', 'type', 'size', 'tmp_name', 'error' ) as $property ) {
			$code[] = sprintf( '$file["%1$s"] = $_FILES["%2$s"]["%1$s"]["%3$s"];', $property, $base, $key );
		}
		$code[] = 'return $file;';

		return eval( implode( PHP_EOL, $code ) );
	}

	/**
	 * Gets the client IP address, considering proxy headers.
	 *
	 * @param string $default Default IP address if none found.
	 * @return string
	 */
	public static function ip( $default = '0.0.0.0' ) {
		$headers = array(
			'HTTP_CLIENT_IP',
			'HTTP_X_FORWARDED_FOR',
			'HTTP_X_FORWARDED',
			'HTTP_X_CLUSTER_CLIENT_IP',
			'HTTP_FORWARDED_FOR',
			'HTTP_FORWARDED',
			'REMOTE_ADDR',
		);
		foreach ( $headers as $key ) {
			if ( isset( $_SERVER[ $key ] ) ) {
				foreach ( explode( ',', $_SERVER[ $key ] ) as $ip ) {
					$ip = trim( $ip );
					if ( filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE ) ) {
						return $ip;
					}
				}
			}
		}

		return $default;
	}

	/**
	 * Gets the user agent string.
	 *
	 * @return string
	 */
	public static function userAgent() {
		return $_SERVER['HTTP_USER_AGENT'] ?? '';
	}

	/**
	 * Removes a parameter from $_GET, $_POST, and $_REQUEST.
	 *
	 * @param string $name Parameter name.
	 */
	public static function remove( $name ) {
		unset( $_GET[ $name ], $_POST[ $name ], $_REQUEST[ $name ] );
	}
}
