<?php

declare(strict_types=1);

namespace SecurWP\Modules\Firewall\SearchTrust;

final class SearchTrustCore {
	private const VERIFIED_SEARCH_ENGINES = [
		'googlebot' => [
			'ua_tokens' => [ 'googlebot' ],
			'dns_suffixes' => [ '.googlebot.com', '.google.com' ],
		],
		'bingbot' => [
			'ua_tokens' => [ 'bingbot' ],
			'dns_suffixes' => [ '.search.msn.com' ],
		],
	];

	private const VERIFIED_SEARCH_CACHE_TTL_POSITIVE = 21600;
	private const VERIFIED_SEARCH_CACHE_TTL_NEGATIVE = 900;

	public static function get_verified_search_engine_claim( string $user_agent ): ?string {
		$user_agent = strtolower( trim( $user_agent ) );
		if ( $user_agent === '' ) {
			return null;
		}

		foreach ( self::VERIFIED_SEARCH_ENGINES as $engine => $config ) {
			foreach ( (array) ( $config['ua_tokens'] ?? [] ) as $token ) {
				$token = strtolower( trim( (string) $token ) );
				if ( $token !== '' && str_contains( $user_agent, $token ) ) {
					return $engine;
				}
			}
		}

		return null;
	}

	/**
	 * @return array<int, string>
	 */
	public static function get_verified_search_dns_suffixes( string $engine ): array {
		$engine = strtolower( trim( $engine ) );
		if ( $engine === '' || ! isset( self::VERIFIED_SEARCH_ENGINES[ $engine ] ) ) {
			return [];
		}

		$suffixes = [];
		foreach ( (array) self::VERIFIED_SEARCH_ENGINES[ $engine ]['dns_suffixes'] as $suffix ) {
			$suffix = strtolower( trim( (string) $suffix ) );
			if ( $suffix !== '' ) {
				$suffixes[ $suffix ] = true;
			}
		}

		return array_keys( $suffixes );
	}

	public static function hostname_matches_verified_search_engine( string $hostname, string $engine ): bool {
		$hostname = strtolower( trim( $hostname ) );
		if ( $hostname === '' ) {
			return false;
		}

		foreach ( self::get_verified_search_dns_suffixes( $engine ) as $suffix ) {
			if ( str_ends_with( $hostname, $suffix ) ) {
				return true;
			}
		}

		return false;
	}

	public static function get_verified_search_cache_ttl( bool $verified ): int {
		return $verified
			? self::VERIFIED_SEARCH_CACHE_TTL_POSITIVE
			: self::VERIFIED_SEARCH_CACHE_TTL_NEGATIVE;
	}

	public static function build_verified_search_cache_key( string $engine, string $ip ): string {
		return 'securwp_vsearch_v3_' . md5( strtolower( trim( $engine ) ) . '|' . trim( $ip ) );
	}

	public static function get_verified_search_cache_shard( string $cache_key, int $shard_count = 32 ): string {
		$cache_key = trim( $cache_key );
		$shard_count = max( 1, $shard_count );

		if ( $cache_key === '' ) {
			return '00';
		}

		$hash = hash( 'crc32b', $cache_key );
		$shard = (int) ( hexdec( substr( $hash, -4 ) ) % $shard_count );

		return str_pad( (string) $shard, 2, '0', STR_PAD_LEFT );
	}

	public static function normalize_ip( string $ip ): string {
		$ip = trim( $ip );
		if ( $ip === '' ) {
			return '';
		}

		if ( strpos( $ip, ':' ) !== false ) {
			$packed = @inet_pton( $ip );
			if ( $packed !== false && strlen( $packed ) === 16 ) {
				if ( substr( $packed, 0, 12 ) === "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" ) {
					$ipv4 = @inet_ntop( substr( $packed, 12 ) );
					if ( $ipv4 !== false ) {
						return $ipv4;
					}
				}
			}
		}

		return $ip;
	}

	public static function should_resolve_verified_search_exception(
		bool $rate_limit_enabled,
		bool $country_check_active,
		bool $community_check_active
	): bool {
		return $rate_limit_enabled || $country_check_active || $community_check_active;
	}

	/**
	 * @param array<string, mixed> $request
	 * @param array<string, mixed> $scope
	 */
	public static function is_verified_search_request_eligible( array $request, array $scope = [] ): bool {
		$method = strtoupper( trim( (string) ( $request['method'] ?? 'GET' ) ) );
		if ( ! in_array( $method, [ 'GET', 'HEAD' ], true ) ) {
			return false;
		}

		$request_uri = (string) ( $request['request_uri'] ?? '' );
		$path = $request_uri !== ''
			? self::request_uri_component( $request_uri, 'path' )
			: (string) ( $request['path'] ?? '' );
		$query = $request_uri !== ''
			? self::request_uri_component( $request_uri, 'query' )
			: (string) ( $request['query'] ?? '' );

		$normalized_path = self::normalize_absolute_request_path( $path );
		if (
			self::request_matches_scope_path( $normalized_path, (array) ( $scope['protected_paths'] ?? [] ) )
			|| self::request_matches_scope_prefix( $normalized_path, (array) ( $scope['protected_prefixes'] ?? [] ) )
		) {
			return false;
		}

		return ! self::has_query_parameter( $query, 'rest_route' );
	}

	public static function request_uri_component( string $request_uri, string $component ): string {
		$uri = explode( '#', $request_uri, 2 )[0];
		$pos = strpos( $uri, '?' );

		if ( $component === 'query' ) {
			return $pos === false ? '' : (string) substr( $uri, $pos + 1 );
		}

		$path = $pos === false ? $uri : (string) substr( $uri, 0, $pos );
		return $path !== '' ? $path : '/';
	}

	/**
	 * @param array<int, string> $paths
	 * @return array<int, string>
	 */
	public static function normalize_request_scope_paths( array $paths ): array {
		$normalized = [];
		foreach ( $paths as $path ) {
			$path = self::normalize_absolute_request_path( (string) $path );
			if ( $path !== '' ) {
				$normalized[ $path ] = true;
			}
		}

		return array_keys( $normalized );
	}

	/**
	 * @param array<int, string> $prefixes
	 * @return array<int, string>
	 */
	public static function normalize_request_scope_prefixes( array $prefixes ): array {
		$normalized = [];
		foreach ( $prefixes as $prefix ) {
			$prefix = self::normalize_absolute_request_path( (string) $prefix );
			if ( $prefix !== '' && $prefix !== '/' ) {
				$normalized[ $prefix ] = true;
			}
		}

		return array_keys( $normalized );
	}

	/**
	 * @param array<string, mixed> $scope
	 */
	public static function build_scope_hash( array $scope ): string {
		$normalized_scope = [
			'protected_paths' => self::normalize_request_scope_paths( (array) ( $scope['protected_paths'] ?? [] ) ),
			'protected_prefixes' => self::normalize_request_scope_prefixes( (array) ( $scope['protected_prefixes'] ?? [] ) ),
		];

		$encoded = json_encode( $normalized_scope, JSON_UNESCAPED_SLASHES );
		if ( ! is_string( $encoded ) ) {
			$encoded = serialize( $normalized_scope );
		}

		return hash( 'sha256', $encoded );
	}

	private static function normalize_absolute_request_path( string $path ): string {
		$path = rawurldecode( trim( $path ) );
		if ( $path === '' ) {
			return '/';
		}

		$path = str_replace( '\\', '/', $path );
		$path = preg_replace( '#/+#', '/', $path ) ?? $path;
		$path = '/' . ltrim( $path, '/' );

		return $path === '/' ? '/' : rtrim( $path, '/' );
	}

	/**
	 * @param array<int, string> $paths
	 */
	private static function request_matches_scope_path( string $path, array $paths ): bool {
		if ( $path === '' ) {
			return false;
		}

		$path = self::normalize_absolute_request_path( $path );
		$normalized_paths = self::normalize_request_scope_paths( $paths );

		return in_array( $path, $normalized_paths, true );
	}

	/**
	 * @param array<int, string> $prefixes
	 */
	private static function request_matches_scope_prefix( string $path, array $prefixes ): bool {
		if ( $path === '' ) {
			return false;
		}

		$path = self::normalize_absolute_request_path( $path );
		foreach ( self::normalize_request_scope_prefixes( $prefixes ) as $prefix ) {
			if ( $path === $prefix || str_starts_with( $path, $prefix . '/' ) ) {
				return true;
			}
		}

		return false;
	}

	private static function has_query_parameter( string $query, string $parameter ): bool {
		$query = ltrim( trim( $query ), '?' );
		$parameter = trim( $parameter );
		if ( $query === '' || $parameter === '' ) {
			return false;
		}

		$params = [];
		parse_str( $query, $params );

		return array_key_exists( $parameter, $params );
	}
}
