<?php
/**
 * Normalize provider errors into DeliveryResult.
 *
 * @package EmailSmtp
 * @since 1.1.0
 */

namespace EmailSmtp\Includes;

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

final class ErrorNormalizer {

	/**
	 * @param mixed $error
	 */
	public static function from_mixed( $error ): DeliveryResult {
		if ( $error instanceof DeliveryResult ) {
			return $error;
		}

		if ( $error instanceof \WP_Error ) {
			$message = $error->get_error_message();
			$code    = (string) $error->get_error_code();
			return self::from_text( $message, $code );
		}

		if ( $error instanceof \Throwable ) {
			return self::from_text( $error->getMessage(), (string) $error->getCode() );
		}

		if ( is_string( $error ) ) {
			return self::from_text( $error );
		}

		return DeliveryResult::failure(
			__( 'An unknown email delivery error occurred.', 'wp-powersuite' ),
			__( 'Unknown error.', 'wp-powersuite' ),
			'unknown'
		);
	}

	public static function from_text( string $message, string $code = '' ): DeliveryResult {
		$sanitized = self::redact( $message );
		$category  = self::categorize( $sanitized, $code );
		$user      = self::user_message_for( $category );
		$retry     = in_array( $category, [ 'timeout', 'network', 'rate_limit', 'temporary', 'provider', 'quota' ], true );

		return DeliveryResult::failure(
			$user,
			$sanitized,
			$category,
			$code,
			$retry,
			self::suggested_action_for( $category )
		);
	}

	public static function redact( string $text ): string {
		$text = preg_replace( '/Bearer\s+[A-Za-z0-9._\-\/=+]+/i', 'Bearer [redacted]', $text ) ?? $text;
		$text = preg_replace( '/Basic\s+[A-Za-z0-9+\/=]+/i', 'Basic [redacted]', $text ) ?? $text;
		$text = preg_replace( '/(api[_-]?key|password|secret|token|authorization|refresh_token|access_token|client_secret|webhook_secret)\s*[:=]\s*\S+/i', '$1=[redacted]', $text ) ?? $text;
		$text = preg_replace( '/ENC2?:[A-Za-z0-9+\/=]+/', 'ENC[redacted]', $text ) ?? $text;
		return sanitize_textarea_field( $text );
	}

	private static function categorize( string $message, string $code ): string {
		$code_key = strtolower( trim( $code ) );
		if ( in_array( $code_key, [ '401', 'unauthorized', 'unauthorised' ], true ) ) {
			return 'authentication';
		}
		if ( in_array( $code_key, [ '403', 'forbidden' ], true ) ) {
			return 'authorization';
		}

		$haystack = strtolower( $message . ' ' . $code );

		// Order matters: avoid bare "auth" (matches "authorized", "author", etc.).
		$map = [
			'sender'         => [ 'from address', 'sender address', 'unverified sender', 'not authorized to send', 'not authorised to send', 'envelope sender', 'invalid from' ],
			'domain'         => [ 'domain not verified', 'not configured for api', 'apiauth', '_mb', 'dkim', 'spf', 'dmarc' ],
			'authentication' => [ 'authentication failed', 'authentication error', 'failed auth', 'unauthorized', 'unauthorised', 'invalid api key', 'invalid credentials', 'smtp authentication', '535', '534', 'login failed' ],
			'authorization'  => [ 'forbidden', 'permission denied', 'not allowed', 'access denied' ],
			'validation'     => [ 'invalid', 'validation', 'malformed', 'bad request' ],
			'recipient'      => [ 'recipient', 'mailbox', 'user unknown', '550' ],
			'rate_limit'     => [ 'rate limit', 'too many', '429', 'throttl' ],
			'timeout'        => [ 'timeout', 'timed out' ],
			'network'        => [ 'could not resolve', 'dns', 'connection refused', 'network' ],
			'tls'            => [ 'ssl', 'tls', 'certificate' ],
			'quota'          => [ 'quota', 'daily limit', 'sending limit' ],
			'sandbox'        => [ 'sandbox' ],
			'temporary'      => [ 'try again', 'temporarily', '421', '450', '451' ],
			'permanent'      => [ 'permanent', '5.1.1', '553' ],
		];

		foreach ( $map as $category => $needles ) {
			foreach ( $needles as $needle ) {
				if ( str_contains( $haystack, $needle ) ) {
					return $category;
				}
			}
		}

		return 'unknown';
	}

	private static function user_message_for( string $category ): string {
		switch ( $category ) {
			case 'authentication':
				return __( 'Authentication failed. Check your credentials.', 'wp-powersuite' );
			case 'authorization':
				return __( 'The credentials do not have permission to send email.', 'wp-powersuite' );
			case 'validation':
				return __( 'The email or provider settings failed validation.', 'wp-powersuite' );
			case 'sender':
				return __( 'The From address is not accepted by the provider.', 'wp-powersuite' );
			case 'recipient':
				return __( 'One or more recipients were rejected.', 'wp-powersuite' );
			case 'domain':
				return __( 'The sending domain is not authorized with the provider (DNS / domain verification).', 'wp-powersuite' );
			case 'rate_limit':
				return __( 'The provider rate limit was reached. Try again later.', 'wp-powersuite' );
			case 'timeout':
				return __( 'The connection timed out.', 'wp-powersuite' );
			case 'network':
				return __( 'A network or DNS error prevented delivery.', 'wp-powersuite' );
			case 'tls':
				return __( 'A TLS/SSL error prevented a secure connection.', 'wp-powersuite' );
			case 'quota':
				return __( 'The provider sending quota was exceeded.', 'wp-powersuite' );
			case 'sandbox':
				return __( 'The provider account is in sandbox mode and restricted recipients.', 'wp-powersuite' );
			case 'temporary':
				return __( 'A temporary provider error occurred. Retrying later may help.', 'wp-powersuite' );
			case 'permanent':
				return __( 'The provider permanently rejected this message.', 'wp-powersuite' );
			default:
				return __( 'Email delivery failed.', 'wp-powersuite' );
		}
	}

	private static function suggested_action_for( string $category ): string {
		switch ( $category ) {
			case 'authentication':
				return __( 'Replace the saved credentials and run Test Connection.', 'wp-powersuite' );
			case 'sender':
			case 'domain':
				return __( 'Verify the From domain DNS (SPF / provider auth records) and that the From address is allowed.', 'wp-powersuite' );
			case 'tls':
				return __( 'Confirm encryption settings and that OpenSSL is available on the server.', 'wp-powersuite' );
			case 'sandbox':
				return __( 'Add the recipient in the provider sandbox allowlist or move the account to production.', 'wp-powersuite' );
			case 'rate_limit':
			case 'quota':
				return __( 'Wait and retry, or review provider plan limits.', 'wp-powersuite' );
			default:
				return __( 'Review Diagnostics and provider documentation for more detail.', 'wp-powersuite' );
		}
	}
}
