<?php
/**
 * Lightweight DNS checks for From-domain deliverability hints.
 *
 * @package EmailSmtp
 */

namespace EmailSmtp\Includes;

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

final class DomainHealth {

	private const CACHE_TTL = HOUR_IN_SECONDS;

	/**
	 * @return array{
	 *   show: bool,
	 *   domain: string,
	 *   from_email: string,
	 *   score: int,
	 *   total: int,
	 *   checked_at: string,
	 *   cached: bool,
	 *   checks: list<array{id: string, label: string, tone: string, detail: string, tip?: string}>
	 * }
	 */
	public static function report_for_settings( array $settings, bool $force_refresh = false, bool $live_if_uncached = true ): array {
		$settings    = ConnectionStore::ensure( $settings );
		$connections = ConnectionStore::all( $settings );
		if ( $connections === [] ) {
			return self::empty_report();
		}

		$default = sanitize_key( (string) ( $settings['default_connection_id'] ?? '' ) );
		$email   = '';
		if ( $default !== '' && isset( $connections[ $default ] ) && is_array( $connections[ $default ] ) ) {
			$email = (string) ( $connections[ $default ]['from_email'] ?? '' );
		}
		if ( $email === '' || ! is_email( $email ) ) {
			foreach ( $connections as $conn ) {
				if ( ! is_array( $conn ) ) {
					continue;
				}
				$candidate = sanitize_email( (string) ( $conn['from_email'] ?? '' ) );
				if ( is_email( $candidate ) ) {
					$email = $candidate;
					break;
				}
			}
		}

		return self::report_for_email( $email, $force_refresh, $live_if_uncached );
	}

	/**
	 * @return array{
	 *   show: bool,
	 *   domain: string,
	 *   from_email: string,
	 *   score: int,
	 *   total: int,
	 *   checked_at: string,
	 *   cached: bool,
	 *   checks: list<array{id: string, label: string, tone: string, detail: string, tip?: string}>
	 * }
	 */
	public static function report_for_email( string $email, bool $force_refresh = false, bool $live_if_uncached = true ): array {
		$email = sanitize_email( $email );
		if ( ! is_email( $email ) ) {
			return self::empty_report();
		}

		$domain = strtolower( (string) substr( strrchr( $email, '@' ), 1 ) );
		if ( $domain === '' ) {
			return self::empty_report();
		}

		$cache_key = 'wpps_ed_dh_' . md5( $domain );
		if ( ! $force_refresh ) {
			$cached = get_transient( $cache_key );
			if ( is_array( $cached ) && ! empty( $cached['checks'] ) ) {
				$cached['cached'] = true;
				$cached['show']   = true;
				return $cached;
			}
			if ( ! $live_if_uncached ) {
				return [
					'show'       => true,
					'domain'     => $domain,
					'from_email' => $email,
					'score'      => 0,
					'total'      => 0,
					'checked_at' => '',
					'cached'     => false,
					'pending'    => true,
					'checks'     => [
						[
							'id'     => 'pending',
							'label'  => __( 'DNS checks', 'wp-powersuite' ),
							'tone'   => 'muted',
							'detail' => __( 'Click Refresh to run domain health checks.', 'wp-powersuite' ),
						],
					],
				];
			}
		}

		if ( ! self::dns_available() ) {
			$report = [
				'show'       => true,
				'domain'     => $domain,
				'from_email' => $email,
				'score'      => 0,
				'total'      => 0,
				'checked_at' => gmdate( 'c' ),
				'cached'     => false,
				'checks'     => [
					[
						'id'     => 'dns',
						'label'  => __( 'DNS lookups', 'wp-powersuite' ),
						'tone'   => 'muted',
						'detail' => __( 'This server cannot query DNS records.', 'wp-powersuite' ),
						'tip'    => __( 'Ask your host to enable dns_get_record / DNS lookups for PHP.', 'wp-powersuite' ),
					],
				],
			];
			set_transient( $cache_key, $report, self::CACHE_TTL );
			return $report;
		}

		$checks = [
			self::check_domain_resolves( $domain ),
			self::check_mx( $domain ),
			self::check_spf( $domain ),
			self::check_dmarc( $domain ),
			self::check_dkim_selectors( $domain ),
		];

		$score = 0;
		$total = 0;
		foreach ( $checks as $check ) {
			if ( ( $check['tone'] ?? '' ) === 'muted' ) {
				continue;
			}
			++$total;
			if ( ( $check['tone'] ?? '' ) === 'success' ) {
				++$score;
			}
		}

		$report = [
			'show'       => true,
			'domain'     => $domain,
			'from_email' => $email,
			'score'      => $score,
			'total'      => $total,
			'checked_at' => gmdate( 'c' ),
			'cached'     => false,
			'checks'     => $checks,
		];
		set_transient( $cache_key, $report, self::CACHE_TTL );
		return $report;
	}

	/**
	 * @return array{
	 *   show: bool,
	 *   domain: string,
	 *   from_email: string,
	 *   score: int,
	 *   total: int,
	 *   checked_at: string,
	 *   cached: bool,
	 *   checks: list<array{id: string, label: string, tone: string, detail: string, tip?: string}>
	 * }
	 */
	private static function empty_report(): array {
		return [
			'show'       => false,
			'domain'     => '',
			'from_email' => '',
			'score'      => 0,
			'total'      => 0,
			'checked_at' => '',
			'cached'     => false,
			'checks'     => [],
		];
	}

	private static function dns_available(): bool {
		return function_exists( 'dns_get_record' );
	}

	/**
	 * @return array{id: string, label: string, tone: string, detail: string, tip?: string}
	 */
	private static function check_domain_resolves( string $domain ): array {
		$a    = @dns_get_record( $domain, DNS_A ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$aaaa = @dns_get_record( $domain, DNS_AAAA ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$ok   = ( is_array( $a ) && $a !== [] ) || ( is_array( $aaaa ) && $aaaa !== [] );

		return [
			'id'     => 'domain',
			'label'  => __( 'Domain DNS', 'wp-powersuite' ),
			'tone'   => $ok ? 'success' : 'warning',
			'detail' => $ok
				? sprintf(
					/* translators: %s: domain */
					__( '%s resolves in public DNS.', 'wp-powersuite' ),
					$domain
				)
				: sprintf(
					/* translators: %s: domain */
					__( 'No A/AAAA records found for %s.', 'wp-powersuite' ),
					$domain
				),
			'tip'    => $ok ? '' : __( 'Confirm the From domain is spelled correctly and published at your DNS host.', 'wp-powersuite' ),
		];
	}

	/**
	 * @return array{id: string, label: string, tone: string, detail: string, tip?: string}
	 */
	private static function check_mx( string $domain ): array {
		$records = @dns_get_record( $domain, DNS_MX ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		$ok      = is_array( $records ) && $records !== [];
		$host    = '';
		if ( $ok ) {
			usort(
				$records,
				static function ( $a, $b ): int {
					return (int) ( $a['pri'] ?? 0 ) <=> (int) ( $b['pri'] ?? 0 );
				}
			);
			$host = (string) ( $records[0]['target'] ?? '' );
		}

		return [
			'id'     => 'mx',
			'label'  => __( 'MX records', 'wp-powersuite' ),
			'tone'   => $ok ? 'success' : 'warning',
			'detail' => $ok
				? sprintf(
					/* translators: 1: domain, 2: mx host */
					__( 'Mail exchangers found for %1$s (primary: %2$s).', 'wp-powersuite' ),
					$domain,
					$host !== '' ? $host : '—'
				)
				: sprintf(
					/* translators: %s: domain */
					__( 'No MX records for %s. Receiving mail may fail; sending can still work.', 'wp-powersuite' ),
					$domain
				),
			'tip'    => __( 'MX is mainly for inbound mail. Missing MX does not block SMTP/API sending.', 'wp-powersuite' ),
		];
	}

	/**
	 * @return array{id: string, label: string, tone: string, detail: string, tip?: string}
	 */
	private static function check_spf( string $domain ): array {
		$txt = self::txt_records( $domain );
		$spf = '';
		foreach ( $txt as $record ) {
			if ( stripos( $record, 'v=spf1' ) === 0 ) {
				$spf = $record;
				break;
			}
		}

		if ( $spf === '' ) {
			return [
				'id'     => 'spf',
				'label'  => __( 'SPF', 'wp-powersuite' ),
				'tone'   => 'warning',
				'detail' => sprintf(
					/* translators: %s: domain */
					__( 'No SPF TXT record found for %s.', 'wp-powersuite' ),
					$domain
				),
				'tip'    => __( 'Add a TXT record starting with v=spf1 that includes your sending provider.', 'wp-powersuite' ),
			];
		}

		$tone   = 'success';
		$detail = __( 'SPF record present.', 'wp-powersuite' );
		$tip    = __( 'Confirm the record authorizes your current mail provider or SMTP relay.', 'wp-powersuite' );

		if ( preg_match( '/\+all\b/i', $spf ) ) {
			$tone   = 'warning';
			$detail = __( 'SPF uses +all, which is unsafe and may hurt deliverability.', 'wp-powersuite' );
			$tip    = __( 'Prefer ~all (soft fail) or -all (hard fail) after authorizing your senders.', 'wp-powersuite' );
		} elseif ( preg_match( '/-all\b/i', $spf ) ) {
			$detail = __( 'SPF present with hard fail (-all).', 'wp-powersuite' );
		} elseif ( preg_match( '/~all\b/i', $spf ) ) {
			$detail = __( 'SPF present with soft fail (~all).', 'wp-powersuite' );
		}

		$includes = preg_match_all( '/\b(?:include|a|mx|ip4|ip6):/i', $spf );
		if ( is_int( $includes ) && $includes > 0 ) {
			$detail .= ' ' . sprintf(
				/* translators: %d: mechanism count */
				_n( '%d sending mechanism found.', '%d sending mechanisms found.', $includes, 'wp-powersuite' ),
				$includes
			);
		}

		return [
			'id'     => 'spf',
			'label'  => __( 'SPF', 'wp-powersuite' ),
			'tone'   => $tone,
			'detail' => $detail,
			'tip'    => $tip,
		];
	}

	/**
	 * @return array{id: string, label: string, tone: string, detail: string, tip?: string}
	 */
	private static function check_dmarc( string $domain ): array {
		$txt   = self::txt_records( '_dmarc.' . $domain );
		$dmarc = '';
		foreach ( $txt as $record ) {
			if ( stripos( $record, 'v=dmarc1' ) === 0 ) {
				$dmarc = $record;
				break;
			}
		}

		if ( $dmarc === '' ) {
			return [
				'id'     => 'dmarc',
				'label'  => __( 'DMARC', 'wp-powersuite' ),
				'tone'   => 'warning',
				'detail' => sprintf(
					/* translators: %s: domain */
					__( 'No DMARC record at _dmarc.%s.', 'wp-powersuite' ),
					$domain
				),
				'tip'    => __( 'Publish a DMARC TXT record (start with p=none, then tighten to quarantine/reject).', 'wp-powersuite' ),
			];
		}

		$policy = 'none';
		if ( preg_match( '/\bp=(none|quarantine|reject)\b/i', $dmarc, $m ) ) {
			$policy = strtolower( $m[1] );
		}

		$tone = 'success';
		$detail = sprintf(
			/* translators: %s: DMARC policy */
			__( 'DMARC present (p=%s).', 'wp-powersuite' ),
			$policy
		);
		$tip = __( 'Monitor aggregate reports before moving from p=none to quarantine or reject.', 'wp-powersuite' );

		if ( $policy === 'none' ) {
			$tone = 'warning';
			$tip  = __( 'p=none only monitors. Raise to quarantine/reject once SPF/DKIM look healthy.', 'wp-powersuite' );
		}

		return [
			'id'     => 'dmarc',
			'label'  => __( 'DMARC', 'wp-powersuite' ),
			'tone'   => $tone,
			'detail' => $detail,
			'tip'    => $tip,
		];
	}

	/**
	 * Probe common DKIM selectors (presence only — not cryptographic validation).
	 *
	 * @return array{id: string, label: string, tone: string, detail: string, tip?: string}
	 */
	private static function check_dkim_selectors( string $domain ): array {
		$selectors = [ 'default', 'google', 'selector1', 'selector2', 'k1', 's1', 's2', 'mail', 'dkim', 'smtp' ];
		$found     = [];

		foreach ( $selectors as $selector ) {
			$host = $selector . '._domainkey.' . $domain;
			foreach ( self::txt_records( $host ) as $record ) {
				if ( stripos( $record, 'v=DKIM1' ) !== false || stripos( $record, 'p=' ) !== false ) {
					$found[] = $selector;
					break;
				}
			}
			if ( count( $found ) >= 3 ) {
				break;
			}
		}

		if ( $found === [] ) {
			return [
				'id'     => 'dkim',
				'label'  => __( 'DKIM', 'wp-powersuite' ),
				'tone'   => 'warning',
				'detail' => __( 'No common DKIM selector records found.', 'wp-powersuite' ),
				'tip'    => __( 'Enable DKIM in your mail provider and publish the CNAME/TXT they give you. Custom selectors may not appear here.', 'wp-powersuite' ),
			];
		}

		return [
			'id'     => 'dkim',
			'label'  => __( 'DKIM', 'wp-powersuite' ),
			'tone'   => 'success',
			'detail' => sprintf(
				/* translators: %s: comma-separated selectors */
				__( 'DKIM selector records found: %s.', 'wp-powersuite' ),
				implode( ', ', $found )
			),
			'tip'    => __( 'Presence check only — your provider still needs to sign outbound mail.', 'wp-powersuite' ),
		];
	}

	/**
	 * @return list<string>
	 */
	private static function txt_records( string $host ): array {
		$records = @dns_get_record( $host, DNS_TXT ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
		if ( ! is_array( $records ) ) {
			return [];
		}

		$out = [];
		foreach ( $records as $row ) {
			if ( ! is_array( $row ) ) {
				continue;
			}
			if ( isset( $row['txt'] ) && is_string( $row['txt'] ) ) {
				$out[] = trim( $row['txt'] );
				continue;
			}
			if ( isset( $row['entries'] ) && is_array( $row['entries'] ) ) {
				$out[] = trim( implode( '', array_map( 'strval', $row['entries'] ) ) );
			}
		}

		return $out;
	}
}
