<?php

declare(strict_types=1);

namespace SecurWP\Modules\Firewall\SearchTrust;

final class SearchTrustDnsVerifier {
	/**
	 * @param callable(string):?bool         $read_cache
	 * @param callable(string,bool,int):void $write_cache
	 */
	public static function verify( string $ip, string $engine, callable $read_cache, callable $write_cache ): bool {
		$ip = SearchTrustCore::normalize_ip( $ip );
		$engine = strtolower( trim( $engine ) );
		if ( $ip === '' || $engine === '' ) {
			return false;
		}

		if ( empty( SearchTrustCore::get_verified_search_dns_suffixes( $engine ) ) ) {
			return false;
		}

		$cache_key = SearchTrustCore::build_verified_search_cache_key( $engine, $ip );
		$cached = $read_cache( $cache_key );
		if ( is_bool( $cached ) ) {
			return $cached;
		}

		$hostname = self::with_dns_socket_timeout(
			static function () use ( $ip ) {
				return @gethostbyaddr( $ip ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Verified-search DNS checks fail closed when reverse resolution is unavailable.
			}
		);

		if ( ! is_string( $hostname ) || $hostname === '' || $hostname === $ip ) {
			$write_cache( $cache_key, false, SearchTrustCore::get_verified_search_cache_ttl( false ) );
			return false;
		}

		if ( ! SearchTrustCore::hostname_matches_verified_search_engine( $hostname, $engine ) ) {
			$write_cache( $cache_key, false, SearchTrustCore::get_verified_search_cache_ttl( false ) );
			return false;
		}

		$forward_ips = self::get_forward_dns_ips( $hostname );
		$verified = in_array( $ip, $forward_ips, true );
		$write_cache( $cache_key, $verified, SearchTrustCore::get_verified_search_cache_ttl( $verified ) );

		return $verified;
	}

	/**
	 * @return array<int, string>
	 */
	private static function get_forward_dns_ips( string $hostname ): array {
		$resolved = [];

		if ( function_exists( 'dns_get_record' ) && defined( 'DNS_A' ) && defined( 'DNS_AAAA' ) ) {
			$records = self::with_dns_socket_timeout(
				static function () use ( $hostname ) {
					return @dns_get_record( $hostname, DNS_A | DNS_AAAA ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Verified-search DNS checks fail closed when forward resolution is unavailable.
				}
			);

			if ( is_array( $records ) ) {
				foreach ( $records as $record ) {
					if ( ! is_array( $record ) ) {
						continue;
					}

					$candidate = '';
					if ( isset( $record['ip'] ) && is_string( $record['ip'] ) ) {
						$candidate = $record['ip'];
					} elseif ( isset( $record['ipv6'] ) && is_string( $record['ipv6'] ) ) {
						$candidate = $record['ipv6'];
					}

					$candidate = SearchTrustCore::normalize_ip( $candidate );
					if ( $candidate !== '' ) {
						$resolved[ $candidate ] = true;
					}
				}
			}
		}

		if ( empty( $resolved ) ) {
			$fallback = self::with_dns_socket_timeout(
				static function () use ( $hostname ) {
					return @gethostbynamel( $hostname ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- Verified-search DNS checks fail closed when IPv4 forward resolution is unavailable.
				}
			);

			if ( is_array( $fallback ) ) {
				foreach ( $fallback as $candidate ) {
					$candidate = SearchTrustCore::normalize_ip( is_string( $candidate ) ? $candidate : '' );
					if ( $candidate !== '' ) {
						$resolved[ $candidate ] = true;
					}
				}
			}
		}

		return array_keys( $resolved );
	}

	/**
	 * @template T
	 * @param callable():T $callback
	 * @return T
	 */
	private static function with_dns_socket_timeout( callable $callback ) {
		$prev_timeout = ini_get( 'default_socket_timeout' );
		if ( ! is_string( $prev_timeout ) || $prev_timeout === '' ) {
			$prev_timeout = '60';
		}

		ini_set( 'default_socket_timeout', '5' ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Narrow DNS timeout only while verifying search-engine ownership.

		try {
			return $callback();
		} finally {
			ini_set( 'default_socket_timeout', $prev_timeout ); // phpcs:ignore Squiz.PHP.DiscouragedFunctions.Discouraged -- Restore worker socket timeout after verified-search DNS checks.
		}
	}
}
