<?php


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

final class TMSMTP_API_Mailers {

	public static function send( $connection, $atts ) {
		$mailer = isset( $connection['mailer'] ) && is_scalar( $connection['mailer'] ) ? sanitize_key( (string) $connection['mailer'] ) : '';

		switch ( $mailer ) {
			case 'sendgrid':
				return self::send_sendgrid( $connection, $atts );
			case 'mailgun':
				return self::send_mailgun( $connection, $atts );
			case 'brevo':
				return self::send_brevo( $connection, $atts );
			case 'amazonses':
				return self::send_amazon_ses( $connection, $atts );
		}

		return new WP_Error( 'tmsmtp_api_mailer', __( 'Unsupported API mailer.', 'titanium-mail-smtp' ), $atts );
	}

	private static function parts( $connection, $atts ) {
		$headers = self::parse_headers( isset( $atts['headers'] ) ? $atts['headers'] : array() );
		$host    = wp_parse_url( network_home_url(), PHP_URL_HOST );
		$host    = is_string( $host ) ? preg_replace( '/^www\./i', '', $host ) : '';
		$host    = $host ? $host : 'localhost.localdomain';

		$original_from_email = ! empty( $headers['from_email'] ) ? $headers['from_email'] : 'wordpress@' . $host;
		$original_from_name  = ! empty( $headers['from_name'] ) ? $headers['from_name'] : 'WordPress';
		$original_from_email = apply_filters( 'wp_mail_from', $original_from_email ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		$original_from_name  = apply_filters( 'wp_mail_from_name', $original_from_name ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound

		$from_email = ! empty( $connection['force_from_email'] ) ? (string) ( $connection['from_email'] ?? '' ) : (string) $original_from_email;
		$from_name  = ! empty( $connection['force_from_name'] ) ? (string) ( $connection['from_name'] ?? '' ) : (string) $original_from_name;
		if ( ! is_email( $from_email ) ) {
			return new WP_Error( 'tmsmtp_api_from', __( 'The configured From Email address is invalid.', 'titanium-mail-smtp' ), $atts );
		}

		$message      = isset( $atts['message'] ) && is_scalar( $atts['message'] ) ? (string) $atts['message'] : '';
		$content_type = ! empty( $headers['content-type'] ) ? implode( ' ', (array) $headers['content-type'] ) : 'text/plain';
		if ( empty( $headers['content-type'] ) ) {
			$content_type = (string) apply_filters( 'wp_mail_content_type', $content_type ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
		}
		$has_html_markup = (bool) preg_match( '/<(?:!doctype\s+html|html|body|table|div|p|br|img|a)(?:\s|>)/i', $message );
		$is_html         = false !== stripos( $content_type, 'text/html' ) || $has_html_markup;
		$html            = $is_html ? ( $has_html_markup ? $message : nl2br( make_clickable( esc_html( $message ) ) ) ) : '';
		$text            = $is_html && $has_html_markup ? self::html_to_text( $message ) : $message;

		$reply_to = '';
		if ( ! empty( $headers['reply-to'] ) ) {
			$reply_to = implode( ', ', self::valid_address_strings( $headers['reply-to'] ) );
		}
		if ( '' === $reply_to && ! empty( $connection['force_from_email'] ) && is_email( $original_from_email ) && strtolower( (string) $original_from_email ) !== strtolower( $from_email ) ) {
			$reply_to = $original_from_name
				? sprintf( '"%s" <%s>', addcslashes( (string) $original_from_name, '"' ), $original_from_email )
				: (string) $original_from_email;
		}

		$custom_headers = array();
		foreach ( $headers as $header_name => $header_values ) {
			if ( in_array( $header_name, array( 'from_email', 'from_name', 'from', 'content-type', 'cc', 'bcc', 'reply-to', 'mime-version' ), true ) ) {
				continue;
			}
			$clean_name = self::sanitize_header_name( $header_name );
			if ( '' === $clean_name ) {
				continue;
			}
			foreach ( (array) $header_values as $header_value ) {
				$clean_value = str_replace( array( "\r", "\n" ), '', (string) $header_value );
				if ( '' !== $clean_value ) {
					$custom_headers[ $clean_name ][] = $clean_value;
				}
			}
		}

		$to = self::valid_address_strings( isset( $atts['to'] ) ? $atts['to'] : array() );
		if ( empty( $to ) ) {
			return new WP_Error( 'tmsmtp_api_recipients', __( 'At least one valid To recipient is required.', 'titanium-mail-smtp' ), $atts );
		}

		$files = self::files( isset( $atts['attachments'] ) ? $atts['attachments'] : array(), $atts );
		if ( ! is_wp_error( $files ) ) {
			$embeds = self::files( isset( $atts['embeds'] ) ? $atts['embeds'] : array(), $atts, true );
			$files  = is_wp_error( $embeds ) ? $embeds : array_merge( $files, $embeds );
		}
		if ( is_wp_error( $files ) ) {
			return $files;
		}

		return array(
			'from_email'  => $from_email,
			'from_name'   => $from_name,
			'to'          => $to,
			'cc'          => self::valid_address_strings( isset( $headers['cc'] ) ? $headers['cc'] : array() ),
			'bcc'         => self::valid_address_strings( isset( $headers['bcc'] ) ? $headers['bcc'] : array() ),
			'reply_to'    => $reply_to,
			'subject'     => isset( $atts['subject'] ) && is_scalar( $atts['subject'] ) ? (string) $atts['subject'] : '',
			'html'        => $html,
			'text'        => $text,
			'attachments' => $files,
			'headers'     => $custom_headers,
		);
	}

	private static function parse_headers( $headers ) {
		$parsed = array( 'cc' => array(), 'bcc' => array(), 'reply-to' => array() );
		if ( is_string( $headers ) ) {
			$headers = preg_split( '/\r\n|\r|\n/', $headers );
		}
		foreach ( (array) $headers as $name => $value ) {
			foreach ( (array) $value as $line ) {
				if ( ! is_scalar( $line ) ) {
					continue;
				}
				$line = is_string( $name ) && ! ctype_digit( $name ) ? $name . ': ' . $line : (string) $line;
				foreach ( preg_split( '/\r\n|\r|\n/', $line ) as $header ) {
					if ( false === strpos( $header, ':' ) ) {
						continue;
					}
					list( $header_name, $header_value ) = array_map( 'trim', explode( ':', $header, 2 ) );
					$key = strtolower( $header_name );
					if ( 'from' === $key && preg_match( '/^(.*)<([^>]+)>$/', $header_value, $matches ) ) {
						$parsed['from_name']  = sanitize_text_field( trim( trim( $matches[1] ), " \t\n\r\0\x0B\"'" ) );
						$parsed['from_email'] = sanitize_email( trim( $matches[2] ) );
					} elseif ( 'from' === $key ) {
						$parsed['from_email'] = sanitize_email( $header_value );
					} elseif ( in_array( $key, array( 'cc', 'bcc', 'reply-to' ), true ) ) {
						$parsed[ $key ] = array_merge( $parsed[ $key ], self::list_values( $header_value ) );
					} else {
						$parsed[ $key ][] = $header_value;
					}
				}
			}
		}
		return $parsed;
	}

	private static function list_values( $values ) {
		$out = array();
		foreach ( (array) $values as $value ) {
			if ( ! is_scalar( $value ) ) {
				continue;
			}
			foreach ( preg_split( '/,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$)/', (string) $value ) as $item ) {
				$item = trim( $item );
				if ( '' !== $item ) {
					$out[] = $item;
				}
			}
		}
		return $out;
	}

	private static function address( $value ) {
		$value = is_scalar( $value ) ? trim( (string) $value ) : '';
		if ( preg_match( '/^(.*)<([^>]+)>$/', $value, $matches ) ) {
			return array(
				'email' => sanitize_email( trim( $matches[2] ) ),
				'name'  => sanitize_text_field( trim( trim( $matches[1] ), " \t\n\r\0\x0B\"'" ) ),
			);
		}
		return array(
			'email' => sanitize_email( $value ),
			'name'  => '',
		);
	}

	private static function valid_address_strings( $values ) {
		$out = array();
		foreach ( self::list_values( $values ) as $value ) {
			$address = self::address( $value );
			if ( ! is_email( $address['email'] ) ) {
				continue;
			}
			$out[] = '' !== $address['name']
				? sprintf( '"%s" <%s>', addcslashes( $address['name'], '"' ), $address['email'] )
				: $address['email'];
		}
		return $out;
	}

	private static function address_objects( $values ) {
		$out = array();
		foreach ( (array) $values as $value ) {
			$address = self::address( $value );
			if ( is_email( $address['email'] ) ) {
				$out[] = array_filter( $address );
			}
		}
		return $out;
	}

	private static function sanitize_header_name( $name ) {
		$name = is_scalar( $name ) ? trim( (string) $name ) : '';
		return preg_match( '/^[A-Za-z0-9!#$%&\'*+.^_`|~-]+$/', $name ) ? $name : '';
	}

	private static function files( $attachments, $atts, $inline = false ) {
		$out = array();
		foreach ( (array) $attachments as $key => $path ) {
			if ( TMSMTP_Plugin::is_empty_file_reference( $path ) ) {
				continue;
			}
			if ( ! is_scalar( $path ) ) {
				return new WP_Error( 'tmsmtp_attachment_invalid', __( 'An email attachment path is invalid.', 'titanium-mail-smtp' ), $atts );
			}
			$args = array(
				'path'        => (string) $path,
				'cid'         => $inline ? (string) $key : '',
				'name'        => basename( (string) $path ),
				'encoding'    => 'base64',
				'type'        => '',
				'disposition' => $inline ? 'inline' : 'attachment',
			);
			if ( $inline ) {
				$args = apply_filters( 'wp_mail_embed_args', $args ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound
			}
			if ( ! is_array( $args ) || ! isset( $args['path'] ) || ! is_scalar( $args['path'] ) ) {
				return new WP_Error( 'tmsmtp_attachment_invalid', __( 'An email attachment path is invalid.', 'titanium-mail-smtp' ), $atts );
			}
			if ( TMSMTP_Plugin::is_empty_file_reference( $args['path'] ) ) {
				continue;
			}
			$path = trim( (string) $args['path'] );
			$size = '' !== $path && is_file( $path ) && is_readable( $path ) ? filesize( $path ) : false;
			if ( false === $size ) {
				return new WP_Error(
					'tmsmtp_attachment_unreadable',
					sprintf(
						/* translators: %s: attachment file name. */
						__( 'The email attachment could not be read: %s', 'titanium-mail-smtp' ),
						sanitize_file_name( basename( $path ) )
					),
					$atts
				);
			}
			$filetype = wp_check_filetype( $path );
			$name     = sanitize_file_name( isset( $args['name'] ) && is_scalar( $args['name'] ) ? (string) $args['name'] : '' ) ?: sanitize_file_name( basename( $path ) );
			$type     = sanitize_mime_type( isset( $args['type'] ) && is_scalar( $args['type'] ) ? (string) $args['type'] : '' ) ?: ( ! empty( $filetype['type'] ) ? $filetype['type'] : 'application/octet-stream' );
			$out[] = array(
				'path'   => $path,
				'name'   => $name,
				'type'   => $type,
				'size'   => (int) $size,
				'inline' => $inline,
				'cid'    => $inline && isset( $args['cid'] ) && is_scalar( $args['cid'] ) ? preg_replace( '/[\r\n]+/', '', (string) $args['cid'] ) : '',
			);
		}
		return $out;
	}

	private static function validate_size( $bytes, $limit, $memory_bytes, $provider, $atts ) {
		if ( $bytes > $limit ) {
			return new WP_Error( 'tmsmtp_' . sanitize_key( $provider ) . '_message_too_large', sprintf( /* translators: %s: email provider name. */ __( 'The email exceeds %s’s message size limit.', 'titanium-mail-smtp' ), $provider ), $atts );
		}
		$memory_limit = wp_convert_hr_to_bytes( ini_get( 'memory_limit' ) );
		if ( $memory_limit > 0 && memory_get_usage( true ) + $memory_bytes > (int) floor( $memory_limit * 0.9 ) ) {
			return new WP_Error( 'tmsmtp_' . sanitize_key( $provider ) . '_memory', sprintf( /* translators: %s: email provider name. */ __( 'There is not enough PHP memory to safely build the %s email.', 'titanium-mail-smtp' ), $provider ), $atts );
		}
		return true;
	}

	private static function encoded_files_size( $files ) {
		$bytes = 0;
		foreach ( $files as $file ) {
			$bytes += (int) ( 4 * ceil( $file['size'] / 3 ) ) + strlen( $file['name'] . $file['type'] . $file['cid'] ) + 160;
		}
		return $bytes;
	}

	private static function html_to_text( $html ) {
		$text = preg_replace( '/<\s*br\s*\/?\s*>/i', "\n", (string) $html );
		$text = preg_replace( '/<\/(?:p|div|li|tr|h[1-6])\s*>/i', "\n", $text );
		$text = html_entity_decode( wp_strip_all_tags( $text, true ), ENT_QUOTES | ENT_HTML5, get_bloginfo( 'charset' ) ?: 'UTF-8' );
		$text = preg_replace( "/\n{3,}/", "\n\n", $text );
		return trim( $text );
	}

	private static function json_post( $url, $headers, $body, $atts, $name, $max_bytes ) {
		$json = wp_json_encode( $body );
		if ( false === $json ) {
			return new WP_Error( 'tmsmtp_' . $name . '_json', __( 'The request could not be encoded.', 'titanium-mail-smtp' ), $atts );
		}
		if ( $max_bytes > 0 && strlen( $json ) > $max_bytes ) {
			return new WP_Error( 'tmsmtp_' . $name . '_message_too_large', sprintf( /* translators: %s: email provider name. */ __( 'The email exceeds %s’s message size limit.', 'titanium-mail-smtp' ), ucfirst( $name ) ), $atts );
		}

		$response = wp_remote_post(
			$url,
			array(
				'timeout'             => 30,
				'redirection'         => 0,
				'limit_response_size' => 262144,
				'headers'             => array_merge(
					array(
						'Accept'       => 'application/json',
						'Content-Type' => 'application/json',
					),
					$headers
				),
				'body'                => $json,
			)
		);

		if ( is_wp_error( $response ) ) {
			return new WP_Error( $response->get_error_code(), $response->get_error_message(), $atts );
		}
		$code = wp_remote_retrieve_response_code( $response );
		if ( $code < 200 || $code >= 300 ) {
			return new WP_Error(
				'tmsmtp_' . $name,
				sprintf(
					/* translators: 1: provider name, 2: HTTP status code, 3: response message. */
					__( '%1$s returned HTTP %2$d: %3$s', 'titanium-mail-smtp' ),
					ucfirst( $name ),
					$code,
					wp_strip_all_tags( wp_remote_retrieve_body( $response ) )
				),
				$atts
			);
		}
		return true;
	}

	private static function encoded_attachments( $files, $filename_key, $content_key, $type_key, $atts, $provider ) {
		$out = array();
		foreach ( $files as $file ) {
			$content = file_get_contents( $file['path'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			if ( false === $content ) {
				return new WP_Error( 'tmsmtp_attachment_read_failed', sprintf( /* translators: %s: attachment file name. */ __( 'The email attachment could not be read: %s', 'titanium-mail-smtp' ), $file['name'] ), $atts );
			}
			$item = array(
				$filename_key => $file['name'],
				$content_key  => base64_encode( $content ),
			);
			if ( $type_key ) {
				$item[ $type_key ] = $file['type'];
			}
			if ( 'sendgrid' === $provider ) {
				$item['disposition'] = $file['inline'] ? 'inline' : 'attachment';
				if ( $file['inline'] ) {
					$item['content_id'] = preg_replace( '/^cid:/i', '', $file['cid'] ?: $file['name'] );
				}
			}
			$out[] = $item;
		}
		return $out;
	}

	private static function send_sendgrid( $connection, $atts ) {
		$key = trim( (string) ( $connection['sendgrid_api_key'] ?? '' ) );
		if ( '' === $key ) {
			return new WP_Error( 'tmsmtp_sendgrid_config', __( 'The SendGrid API key is missing.', 'titanium-mail-smtp' ), $atts );
		}
		$parts = self::parts( $connection, $atts );
		if ( is_wp_error( $parts ) ) {
			return $parts;
		}

		$personalization = array( 'to' => self::address_objects( $parts['to'] ) );
		if ( $parts['cc'] ) {
			$personalization['cc'] = self::address_objects( $parts['cc'] );
		}
		if ( $parts['bcc'] ) {
			$personalization['bcc'] = self::address_objects( $parts['bcc'] );
		}
		foreach ( $parts['headers'] as $name => $values ) {
			$personalization['headers'][ $name ] = implode( ', ', $values );
		}
		if ( empty( $personalization['to'] ) && empty( $personalization['cc'] ) && empty( $personalization['bcc'] ) ) {
			return new WP_Error( 'tmsmtp_sendgrid_recipients', __( 'No valid recipient was provided.', 'titanium-mail-smtp' ), $atts );
		}

		$body = array(
			'personalizations' => array( $personalization ),
			'from'             => array_filter( array( 'email' => $parts['from_email'], 'name' => $parts['from_name'] ) ),
			'subject'          => $parts['subject'],
			'content'          => array( array( 'type' => 'text/plain', 'value' => '' !== $parts['text'] ? $parts['text'] : ' ' ) ),
		);
		if ( '' !== $parts['html'] ) {
			$body['content'][] = array( 'type' => 'text/html', 'value' => $parts['html'] );
		}
		if ( $parts['reply_to'] ) {
			$reply = self::address( $parts['reply_to'] );
			if ( is_email( $reply['email'] ) ) {
				$body['reply_to'] = array_filter( $reply );
			}
		}
		$estimate = strlen( (string) wp_json_encode( $body ) ) + self::encoded_files_size( $parts['attachments'] );
		$valid    = self::validate_size( $estimate, 30 * MB_IN_BYTES, $estimate * 3, 'SendGrid', $atts );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		$attachments = self::encoded_attachments( $parts['attachments'], 'filename', 'content', 'type', $atts, 'sendgrid' );
		if ( is_wp_error( $attachments ) ) {
			return $attachments;
		}
		if ( $attachments ) {
			$body['attachments'] = $attachments;
		}
		return self::json_post( 'https://api.sendgrid.com/v3/mail/send', array( 'Authorization' => 'Bearer ' . $key ), $body, $atts, 'sendgrid', 30 * MB_IN_BYTES );
	}

	private static function send_brevo( $connection, $atts ) {
		$key = trim( (string) ( $connection['brevo_api_key'] ?? '' ) );
		if ( '' === $key ) {
			return new WP_Error( 'tmsmtp_brevo_config', __( 'The Brevo API key is missing.', 'titanium-mail-smtp' ), $atts );
		}
		$parts = self::parts( $connection, $atts );
		if ( is_wp_error( $parts ) ) {
			return $parts;
		}

		$body = array(
			'sender'      => array_filter( array( 'email' => $parts['from_email'], 'name' => $parts['from_name'] ) ),
			'to'          => self::address_objects( $parts['to'] ),
			'subject'     => $parts['subject'],
			'textContent' => $parts['text'],
		);
		if ( $parts['cc'] ) {
			$body['cc'] = self::address_objects( $parts['cc'] );
		}
		if ( $parts['bcc'] ) {
			$body['bcc'] = self::address_objects( $parts['bcc'] );
		}
		if ( empty( $body['to'] ) && empty( $body['cc'] ) && empty( $body['bcc'] ) ) {
			return new WP_Error( 'tmsmtp_brevo_recipients', __( 'No valid recipient was provided.', 'titanium-mail-smtp' ), $atts );
		}
		if ( '' !== $parts['html'] ) {
			$body['htmlContent'] = $parts['html'];
		}
		if ( $parts['reply_to'] ) {
			$reply = self::address( $parts['reply_to'] );
			if ( is_email( $reply['email'] ) ) {
				$body['replyTo'] = array_filter( $reply );
			}
		}
		foreach ( $parts['headers'] as $name => $values ) {
			$body['headers'][ $name ] = implode( ', ', $values );
		}
		foreach ( $parts['attachments'] as $file ) {
			if ( $file['inline'] ) {
				return new WP_Error( 'tmsmtp_brevo_inline_unsupported', __( 'Brevo’s API does not support WordPress inline embeds. Use a regular attachment or another mailer.', 'titanium-mail-smtp' ), $atts );
			}
		}
		$message_size = strlen( (string) wp_json_encode( $body ) );
		foreach ( $parts['attachments'] as $file ) {
			$message_size += $file['size'] + strlen( $file['name'] . $file['type'] ) + 64;
		}
		$encoded_size = strlen( (string) wp_json_encode( $body ) ) + self::encoded_files_size( $parts['attachments'] );
		$valid        = self::validate_size( $message_size, 20 * MB_IN_BYTES, $encoded_size * 3, 'Brevo', $atts );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		$attachments = self::encoded_attachments( $parts['attachments'], 'name', 'content', '', $atts, 'brevo' );
		if ( is_wp_error( $attachments ) ) {
			return $attachments;
		}
		if ( $attachments ) {
			$body['attachment'] = $attachments;
		}
		return self::json_post( 'https://api.brevo.com/v3/smtp/email', array( 'api-key' => $key ), $body, $atts, 'brevo', 0 );
	}

	public static function valid_mailgun_domain( $domain ) {
		$domain = is_scalar( $domain ) ? trim( strtolower( (string) $domain ) ) : '';
		return 1 === preg_match( '/^(?=.{1,253}$)(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z]{2,63}$/i', $domain );
	}

	private static function send_mailgun( $connection, $atts ) {
		$key    = trim( (string) ( $connection['mailgun_api_key'] ?? '' ) );
		$domain = trim( (string) ( $connection['mailgun_domain'] ?? '' ) );
		if ( '' === $key || ! self::valid_mailgun_domain( $domain ) ) {
			return new WP_Error( 'tmsmtp_mailgun_config', __( 'A valid Mailgun API key and sending domain are required.', 'titanium-mail-smtp' ), $atts );
		}
		$parts = self::parts( $connection, $atts );
		if ( is_wp_error( $parts ) ) {
			return $parts;
		}

		$boundary = '--------------------------' . wp_generate_password( 24, false, false );
		$eol      = "\r\n";
		$body     = '';
		$append   = static function ( $name, $value ) use ( &$body, $boundary, $eol ) {
			$body .= '--' . $boundary . $eol;
			$body .= 'Content-Disposition: form-data; name="' . $name . '"' . $eol . $eol;
			$body .= $value . $eol;
		};

		$append( 'from', $parts['from_name'] ? sprintf( '"%s" <%s>', addcslashes( $parts['from_name'], '"' ), $parts['from_email'] ) : $parts['from_email'] );
		foreach ( array( 'to', 'cc', 'bcc' ) as $field ) {
			foreach ( $parts[ $field ] as $value ) {
				$append( $field, $value );
			}
		}
		$append( 'subject', $parts['subject'] );
		$append( 'text', $parts['text'] );
		if ( '' !== $parts['html'] ) {
			$append( 'html', $parts['html'] );
		}
		if ( $parts['reply_to'] ) {
			$append( 'h:Reply-To', $parts['reply_to'] );
		}
		foreach ( $parts['headers'] as $name => $values ) {
			foreach ( $values as $value ) {
				$append( 'h:' . $name, $value );
			}
		}

		$estimate = strlen( $body ) + 64;
		foreach ( $parts['attachments'] as $file ) {
			$estimate += $file['size'] + strlen( $file['name'] . $file['type'] . $file['cid'] ) + 256;
		}
		$valid = self::validate_size( $estimate, 25 * MB_IN_BYTES, $estimate * 3, 'Mailgun', $atts );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		foreach ( $parts['attachments'] as $file ) {
			$content = file_get_contents( $file['path'] ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			if ( false === $content ) {
				return new WP_Error( 'tmsmtp_attachment_read_failed', sprintf( /* translators: %s: attachment file name. */ __( 'The email attachment could not be read: %s', 'titanium-mail-smtp' ), $file['name'] ), $atts );
			}
			$file_name = $file['inline'] && $file['cid'] ? preg_replace( '/^cid:/i', '', $file['cid'] ) : $file['name'];
			$body     .= '--' . $boundary . $eol;
			$body     .= 'Content-Disposition: form-data; name="' . ( $file['inline'] ? 'inline' : 'attachment' ) . '"; filename="' . str_replace( array( '"', "\r", "\n" ), '', $file_name ) . '"' . $eol;
			$body     .= 'Content-Type: ' . $file['type'] . $eol . $eol . $content . $eol;
		}
		$body .= '--' . $boundary . '--' . $eol;
		if ( strlen( $body ) > 25 * MB_IN_BYTES ) {
			return new WP_Error( 'tmsmtp_mailgun_message_too_large', __( 'The email exceeds Mailgun’s message size limit.', 'titanium-mail-smtp' ), $atts );
		}

		$base     = 'EU' === strtoupper( (string) ( $connection['mailgun_region'] ?? 'US' ) ) ? 'https://api.eu.mailgun.net' : 'https://api.mailgun.net';
		$response = wp_remote_post(
			$base . '/v3/' . rawurlencode( $domain ) . '/messages',
			array(
				'timeout'             => 30,
				'redirection'         => 0,
				'limit_response_size' => 262144,
				'headers'             => array(
					'Authorization' => 'Basic ' . base64_encode( 'api:' . $key ),
					'Content-Type'  => 'multipart/form-data; boundary=' . $boundary,
				),
				'body'                => $body,
			)
		);
		if ( is_wp_error( $response ) ) {
			return new WP_Error( $response->get_error_code(), $response->get_error_message(), $atts );
		}
		$code = wp_remote_retrieve_response_code( $response );
		if ( $code >= 200 && $code < 300 ) {
			return true;
		}
		return new WP_Error( 'tmsmtp_mailgun', sprintf( /* translators: 1: HTTP status code, 2: response message. */ __( 'Mailgun returned HTTP %1$d: %2$s', 'titanium-mail-smtp' ), $code, wp_strip_all_tags( wp_remote_retrieve_body( $response ) ) ), $atts );
	}

	private static function raw_mime( $connection, $atts ) {
		$parts = self::parts( $connection, $atts );
		if ( is_wp_error( $parts ) ) {
			return $parts;
		}
		if ( empty( $parts['to'] ) && empty( $parts['cc'] ) && empty( $parts['bcc'] ) ) {
			return new WP_Error( 'tmsmtp_ses_recipients', __( 'No valid recipient was provided.', 'titanium-mail-smtp' ), $atts );
		}

		$estimate = strlen( $parts['html'] . $parts['text'] . $parts['subject'] ) + self::encoded_files_size( $parts['attachments'] ) + 4096;
		$valid    = self::validate_size( $estimate, 40 * MB_IN_BYTES, $estimate * 4, 'Amazon SES', $atts );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		if ( ! class_exists( '\\PHPMailer\\PHPMailer\\PHPMailer' ) ) {
			require_once ABSPATH . WPINC . '/PHPMailer/PHPMailer.php';
			require_once ABSPATH . WPINC . '/PHPMailer/Exception.php';
		}

		try {
			$mail          = new \PHPMailer\PHPMailer\PHPMailer( true );
			$mail->CharSet = get_bloginfo( 'charset' ) ?: 'UTF-8';
			$mail->setFrom( $parts['from_email'], $parts['from_name'], false );
			foreach ( array( 'to' => 'addAddress', 'cc' => 'addCC', 'bcc' => 'addBCC' ) as $field => $method ) {
				foreach ( $parts[ $field ] as $value ) {
					$address = self::address( $value );
					$mail->{$method}( $address['email'], $address['name'] );
				}
			}
			if ( $parts['reply_to'] ) {
				$address = self::address( $parts['reply_to'] );
				if ( is_email( $address['email'] ) ) {
					$mail->addReplyTo( $address['email'], $address['name'] );
				}
			}
			$mail->Subject = $parts['subject'];
			foreach ( $parts['headers'] as $name => $values ) {
				foreach ( $values as $value ) {
					$mail->addCustomHeader( $name, $value );
				}
			}
			if ( '' !== $parts['html'] ) {
				$mail->isHTML( true );
				$mail->Body    = $parts['html'];
				$mail->AltBody = $parts['text'];
			} else {
				$mail->Body = $parts['text'];
			}
			foreach ( $parts['attachments'] as $file ) {
				if ( $file['inline'] ) {
					$mail->addEmbeddedImage( $file['path'], preg_replace( '/^cid:/i', '', $file['cid'] ?: $file['name'] ), $file['name'], 'base64', $file['type'] );
				} else {
					$mail->addAttachment( $file['path'], $file['name'], 'base64', $file['type'] );
				}
			}
			if ( ! $mail->preSend() ) {
				return new WP_Error( 'tmsmtp_ses_mime', __( 'Could not build the MIME message.', 'titanium-mail-smtp' ), $atts );
			}
			$raw = $mail->getSentMIMEMessage();
			if ( strlen( $raw ) > 40 * MB_IN_BYTES ) {
				return new WP_Error( 'tmsmtp_amazonses_message_too_large', __( 'The email exceeds Amazon SES’s message size limit.', 'titanium-mail-smtp' ), $atts );
			}
			return $raw;
		} catch ( Throwable $exception ) {
			return new WP_Error( 'tmsmtp_ses_mime', $exception->getMessage(), $atts );
		}
	}

	private static function send_amazon_ses( $connection, $atts ) {
		$access = trim( (string) ( $connection['ses_access_key'] ?? '' ) );
		$secret = trim( (string) ( $connection['ses_secret_key'] ?? '' ) );
		$region = trim( (string) ( $connection['ses_region'] ?? '' ) );
		if ( '' === $access || '' === $secret || ! preg_match( '/^[a-z]{2}(?:-gov)?-[a-z0-9-]+-\d$/', $region ) ) {
			return new WP_Error( 'tmsmtp_ses_config', __( 'A valid Amazon SES access key, secret key, and region are required.', 'titanium-mail-smtp' ), $atts );
		}
		$raw = self::raw_mime( $connection, $atts );
		if ( is_wp_error( $raw ) ) {
			return $raw;
		}

		$encoded_size = (int) ( 4 * ceil( strlen( $raw ) / 3 ) ) + 128;
		$valid        = self::validate_size( strlen( $raw ), 40 * MB_IN_BYTES, $encoded_size * 2, 'Amazon SES', $atts );
		if ( is_wp_error( $valid ) ) {
			return $valid;
		}
		$host    = 'email.' . $region . '.amazonaws.com';
		$url     = 'https://' . $host . '/v2/email/outbound-emails';
		$payload = wp_json_encode( array( 'Content' => array( 'Raw' => array( 'Data' => base64_encode( $raw ) ) ) ) );
		if ( false === $payload ) {
			return new WP_Error( 'tmsmtp_ses_json', __( 'The Amazon SES request could not be encoded.', 'titanium-mail-smtp' ), $atts );
		}

		$amz_date          = gmdate( 'Ymd\THis\Z' );
		$date              = gmdate( 'Ymd' );
		$payload_hash      = hash( 'sha256', $payload );
		$canonical_headers = "content-type:application/json\nhost:{$host}\nx-amz-content-sha256:{$payload_hash}\nx-amz-date:{$amz_date}\n";
		$signed_headers    = 'content-type;host;x-amz-content-sha256;x-amz-date';
		$session_token     = trim( (string) ( $connection['ses_session_token'] ?? '' ) );
		if ( '' !== $session_token ) {
			$canonical_headers .= 'x-amz-security-token:' . $session_token . "\n";
			$signed_headers    .= ';x-amz-security-token';
		}

		$canonical_request = "POST\n/v2/email/outbound-emails\n\n{$canonical_headers}\n{$signed_headers}\n{$payload_hash}";
		$scope             = $date . '/' . $region . '/ses/aws4_request';
		$string_to_sign    = "AWS4-HMAC-SHA256\n{$amz_date}\n{$scope}\n" . hash( 'sha256', $canonical_request );
		$k_date            = hash_hmac( 'sha256', $date, 'AWS4' . $secret, true );
		$k_region          = hash_hmac( 'sha256', $region, $k_date, true );
		$k_service         = hash_hmac( 'sha256', 'ses', $k_region, true );
		$k_signing         = hash_hmac( 'sha256', 'aws4_request', $k_service, true );
		$signature         = hash_hmac( 'sha256', $string_to_sign, $k_signing );

		$headers = array(
			'Content-Type'         => 'application/json',
			'Host'                 => $host,
			'X-Amz-Date'           => $amz_date,
			'X-Amz-Content-Sha256' => $payload_hash,
			'Authorization'        => 'AWS4-HMAC-SHA256 Credential=' . $access . '/' . $scope . ', SignedHeaders=' . $signed_headers . ', Signature=' . $signature,
		);
		if ( '' !== $session_token ) {
			$headers['X-Amz-Security-Token'] = $session_token;
		}
		$response = wp_remote_post(
			$url,
			array(
				'timeout'             => 30,
				'redirection'         => 0,
				'limit_response_size' => 262144,
				'headers'             => $headers,
				'body'                => $payload,
			)
		);
		if ( is_wp_error( $response ) ) {
			return new WP_Error( $response->get_error_code(), $response->get_error_message(), $atts );
		}
		$code = wp_remote_retrieve_response_code( $response );
		if ( $code >= 200 && $code < 300 ) {
			return true;
		}
		return new WP_Error( 'tmsmtp_ses', sprintf( /* translators: 1: HTTP status code, 2: response message. */ __( 'Amazon SES returned HTTP %1$d: %2$s', 'titanium-mail-smtp' ), $code, wp_strip_all_tags( wp_remote_retrieve_body( $response ) ) ), $atts );
	}
}
