<?php
/**
 * Mailgun Messages API provider.
 *
 * @package EmailSmtp
 * @link https://documentation.mailgun.com/docs/mailgun/api-reference/send/mailgun/messages/post-v3--domain-name--messages
 */

namespace EmailSmtp\Providers\Mailgun;

use EmailSmtp\Includes\AbstractApiProvider;
use EmailSmtp\Includes\ApiHttpClient;
use EmailSmtp\Includes\DeliveryResult;
use EmailSmtp\Includes\EmailMessage;
use EmailSmtp\Includes\SmtpSettingsHelper;

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

final class Provider extends AbstractApiProvider {

	/** Combined To + Cc + Bcc cap per Messages API call. */
	private const RECIPIENT_CAP = 1000;

	public function get_id(): string {
		return 'mailgun';
	}

	public function get_name(): string {
		return __( 'Mailgun', 'wp-powersuite' );
	}

	public function get_description(): string {
		return __( 'Send through Mailgun using a sending domain and private API key.', 'wp-powersuite' );
	}

	public function get_recommended_use(): string {
		return __( 'Transactional and bulk mail with domain verification.', 'wp-powersuite' );
	}

	public function get_docs_url(): string {
		return 'https://documentation.mailgun.com/docs/mailgun/api-reference/api-overview';
	}

	public function get_icon(): string {
		return 'dashicons-email-alt2';
	}

	public function get_required_fields(): array {
		return [ 'mailgun_api_key', 'mailgun_domain' ];
	}


	/**
	 * @param array<string, mixed> $settings
	 * @return true|\WP_Error
	 */
	public function validate_settings( array $settings ) {
		$key = trim( (string) ( $settings['mailgun_api_key'] ?? '' ) );
		if ( $key === '' && ! defined( 'WP_POWERSUITE_MAILGUN_API_KEY' ) ) {
			return new \WP_Error( 'missing_key', __( 'Mailgun API key is required.', 'wp-powersuite' ) );
		}
		$domain = SmtpSettingsHelper::sanitizeHost( (string) ( $settings['mailgun_domain'] ?? '' ) );
		if ( $domain === '' ) {
			return new \WP_Error( 'missing_domain', __( 'Mailgun sending domain is required.', 'wp-powersuite' ) );
		}
		return true;
	}

	public function connect( array $settings ): DeliveryResult {
		$key    = $this->secret( $settings, 'mailgun_api_key', 'WP_POWERSUITE_MAILGUN_API_KEY' );
		$domain = SmtpSettingsHelper::sanitizeHost( (string) ( $settings['mailgun_domain'] ?? '' ) );
		if ( $key === '' || $domain === '' ) {
			return DeliveryResult::failure( __( 'Mailgun API key and sending domain are required.', 'wp-powersuite' ), '', 'validation' );
		}

		$host     = $this->api_host( $settings );
		$response = ApiHttpClient::request(
			'GET',
			'https://' . $host . '/v3/domains/' . rawurlencode( $domain ),
			[ 'Authorization' => 'Basic ' . base64_encode( 'api:' . $key ) ]
		);

		return ApiHttpClient::to_delivery_result(
			$response,
			static function () {
				return [ 'message' => __( 'Mailgun credentials authenticated successfully.', 'wp-powersuite' ) ];
			}
		);
	}

	public function send( EmailMessage $message, array $settings ): DeliveryResult {
		$missing = $this->require_from( $message );
		if ( $missing ) {
			return $missing;
		}

		$key    = $this->secret( $settings, 'mailgun_api_key', 'WP_POWERSUITE_MAILGUN_API_KEY' );
		$domain = SmtpSettingsHelper::sanitizeHost( (string) ( $settings['mailgun_domain'] ?? '' ) );
		if ( $key === '' || $domain === '' ) {
			return DeliveryResult::failure( __( 'Mailgun API key and sending domain are required.', 'wp-powersuite' ), '', 'validation' );
		}

		$remaining = self::RECIPIENT_CAP;
		$to        = $this->take_recipients( $message->get_to(), $remaining );
		if ( $to === [] ) {
			return DeliveryResult::failure( __( 'At least one recipient is required.', 'wp-powersuite' ), 'missing_to', 'recipient' );
		}

		$from   = $this->from_parts( $message );
		$fields = [
			'from'    => $this->format_address( $from['email'], $from['name'] ),
			'to'      => implode( ', ', $to ),
			'subject' => $message->get_subject(),
		];

		if ( $message->is_html() ) {
			$fields['html'] = $message->get_body();
			$fields['text'] = $this->plain_text_body( $message );
		} else {
			$fields['text'] = $this->plain_text_body( $message );
		}

		$cc = $this->take_recipients( $message->get_cc(), $remaining );
		if ( $cc !== [] ) {
			$fields['cc'] = implode( ', ', $cc );
		}
		$bcc = $this->take_recipients( $message->get_bcc(), $remaining );
		if ( $bcc !== [] ) {
			$fields['bcc'] = implode( ', ', $bcc );
		}
		if ( is_email( $message->get_reply_to_email() ) ) {
			$fields['h:Reply-To'] = $this->format_address( $message->get_reply_to_email(), $message->get_reply_to_name() );
		}

		foreach ( $this->safe_custom_headers( $message ) as $name => $value ) {
			$fields[ 'h:' . $name ] = $value;
		}

		$host = $this->api_host( $settings );
		$url  = 'https://' . $host . '/v3/' . rawurlencode( $domain ) . '/messages';
		$auth = [ 'Authorization' => 'Basic ' . base64_encode( 'api:' . $key ) ];

		// Attachments require multipart/form-data with raw file bytes (not base64).
		$attachments = $this->attachments_as_binary( $message );
		if ( $attachments === [] ) {
			$response = ApiHttpClient::request(
				'POST',
				$url,
				$auth + [ 'Content-Type' => 'application/x-www-form-urlencoded' ],
				http_build_query( $fields )
			);
		} else {
			$boundary = 'wpps_' . wp_generate_password( 24, false, false );
			$body     = '';
			foreach ( $fields as $name => $value ) {
				$body .= '--' . $boundary . "\r\n";
				$body .= 'Content-Disposition: form-data; name="' . $this->multipart_name( (string) $name ) . '"' . "\r\n\r\n";
				$body .= (string) $value . "\r\n";
			}
			foreach ( $attachments as $attachment ) {
				$filename = $this->multipart_filename( $attachment['name'] );
				$type     = $attachment['type'] !== '' ? $attachment['type'] : 'application/octet-stream';
				$body    .= '--' . $boundary . "\r\n";
				$body    .= 'Content-Disposition: form-data; name="attachment"; filename="' . $filename . '"' . "\r\n";
				$body    .= 'Content-Type: ' . $type . "\r\n\r\n";
				$body    .= $attachment['content'] . "\r\n";
			}
			$body .= '--' . $boundary . '--';

			$response = ApiHttpClient::request(
				'POST',
				$url,
				$auth + [ 'Content-Type' => 'multipart/form-data; boundary=' . $boundary ],
				$body
			);
		}

		return ApiHttpClient::to_delivery_result(
			$response,
			static function ( array $res ) {
				$id = is_array( $res['json'] ) ? (string) ( $res['json']['id'] ?? '' ) : '';
				return [
					'message' => __( 'Mailgun accepted the message.', 'wp-powersuite' ),
					'id'      => $id,
				];
			}
		);
	}

	/**
	 * @param array<string, mixed> $settings
	 */
	private function api_host( array $settings ): string {
		$region = sanitize_key( (string) ( $settings['mailgun_region'] ?? 'us' ) );
		return $region === 'eu' ? 'api.eu.mailgun.net' : 'api.mailgun.net';
	}

	/**
	 * @return list<array{name:string, content:string, type:string}>
	 */
	private function attachments_as_binary( EmailMessage $message ): array {
		$out = [];
		foreach ( $message->get_attachments() as $path ) {
			$path = (string) $path;
			if ( $path === '' || ! is_readable( $path ) ) {
				continue;
			}
			$contents = file_get_contents( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
			if ( $contents === false ) {
				continue;
			}
			$name = basename( $path );
			$type = 'application/octet-stream';
			if ( function_exists( 'mime_content_type' ) ) {
				$detected = mime_content_type( $path );
				if ( is_string( $detected ) && $detected !== '' ) {
					$type = $detected;
				}
			}
			$out[] = [
				'name'    => $name,
				'content' => $contents,
				'type'    => $type,
			];
		}
		return $out;
	}

	private function multipart_name( string $name ): string {
		return str_replace( [ "\r", "\n", '"' ], '', $name );
	}

	private function multipart_filename( string $name ): string {
		$name = sanitize_file_name( $name );
		if ( $name === '' ) {
			$name = 'attachment.bin';
		}
		return str_replace( [ "\r", "\n", '"' ], '', $name );
	}
}
