<?php
/**
 * SendGrid REST transport.
 *
 * @package MailDoctor
 */

namespace MailDoctor\Transports;

use WP_Error;

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

/**
 * Handles SendGrid Web API deliveries.
 */
class SendGrid {
	/**
	 * Endpoint base URL.
	 */
	const API_BASE = 'https://api.sendgrid.com/v3';

	/**
	 * Dispatch email via SendGrid v3 API.
	 *
	 * @param array<string,mixed> $args      Normalized mail args.
	 * @param array<string,mixed> $settings  Plugin settings.
	 * @return array<string,mixed>|WP_Error
	 */
	public static function send( array $args, array $settings ) {
		$api_key = trim( (string) ( $settings['sendgrid_api_key'] ?? '' ) );
		if ( '' === $api_key ) {
			return new WP_Error( 'mail_doctor_sendgrid_missing_key', __( 'SendGrid API key is not configured.', 'mail-doctor' ) );
		}

		$recipients = Utils::recipients_from_args( $args );
		if ( empty( $recipients['to'] ) && empty( $recipients['cc'] ) && empty( $recipients['bcc'] ) ) {
			return new WP_Error( 'mail_doctor_sendgrid_missing_recipients', __( 'No recipients were supplied for the message.', 'mail-doctor' ) );
		}

		$sender = Utils::sender_from_settings( $settings );
		if ( empty( $sender['email'] ) || ! is_email( $sender['email'] ) ) {
			return new WP_Error( 'mail_doctor_sendgrid_invalid_sender', __( 'A valid From email address is required to send via SendGrid.', 'mail-doctor' ) );
		}

		$bodies  = Utils::message_bodies( $args );
		$content = self::build_content( $bodies );
		$payload = array(
			'personalizations' => array( self::build_personalization( $recipients ) ),
			'from'             => self::format_address_map( $sender ),
			'subject'          => (string) ( $args['subject'] ?? '' ),
			'content'          => $content,
		);

		if ( ! empty( $recipients['reply_to'] ) ) {
			$payload['reply_to'] = self::format_address_map( $recipients['reply_to'] );
		}

		$headers_map = Utils::headers_to_map( $args['headers'] ?? array() );
		if ( ! empty( $headers_map ) ) {
			$filtered_headers = self::filter_headers( $headers_map );
			if ( ! empty( $filtered_headers ) ) {
				$payload['headers'] = $filtered_headers;
			}
		}

		$attachments = Utils::attachment_payloads( $args['attachments'] ?? array() );
		if ( ! empty( $attachments ) ) {
			$payload['attachments'] = array();
			foreach ( $attachments as $attachment ) {
				$payload['attachments'][] = array(
					'content'  => $attachment['content'],
					'type'     => $attachment['type'],
					'filename' => $attachment['name'],
				);
			}
		}

		$response = wp_remote_post(
			self::API_BASE . '/mail/send',
			array(
				'headers' => array(
					'Authorization' => 'Bearer ' . $api_key,
					'Content-Type'  => 'application/json',
				),
				'timeout' => 30,
				'body'    => wp_json_encode( $payload ),
			)
		);

		if ( is_wp_error( $response ) ) {
			return new WP_Error( 'mail_doctor_sendgrid_request_error', $response->get_error_message(), array( 'transport' => 'sendgrid' ) );
		}

		$code = wp_remote_retrieve_response_code( $response );
		$body = wp_remote_retrieve_body( $response );

		if ( 202 !== $code ) {
			$message = __( 'SendGrid API request failed.', 'mail-doctor' );
			if ( $body ) {
				$json = json_decode( $body, true );
				if ( isset( $json['errors'][0]['message'] ) ) {
					$message = (string) $json['errors'][0]['message'];
				}
			}

			return new WP_Error(
				'mail_doctor_sendgrid_failed',
				$message,
				array(
					'status'   => $code,
					'response' => $body,
				)
			);
		}

		$provider_code = wp_remote_retrieve_header( $response, 'x-message-id' );

		return array(
			'provider_code' => $provider_code ? (string) $provider_code : '',
			'status'        => (int) $code,
			'raw'           => $body,
		);
	}

	/**
	 * Perform credential verification against SendGrid.
	 *
	 * @param array<string,mixed> $settings Plugin settings.
	 * @return array<string,mixed>
	 */
	public static function check_connection( array $settings ): array {
		$api_key = trim( (string) ( $settings['sendgrid_api_key'] ?? '' ) );
		if ( '' === $api_key ) {
			return array(
				'success' => false,
				'message' => __( 'SendGrid API key is missing.', 'mail-doctor' ),
				'data'    => array(
					'host'       => 'api.sendgrid.com',
					'encryption' => 'https',
					'auth'       => false,
					'debug'      => array(),
				),
			);
		}

		$url   = self::API_BASE . '/user/account';
		$debug = array( sprintf( 'GET %s', $url ) );

		$response = wp_remote_get(
			$url,
			array(
				'timeout' => 15,
				'headers' => array(
					'Authorization' => 'Bearer ' . $api_key,
				),
			)
		);

		if ( is_wp_error( $response ) ) {
			$debug[] = $response->get_error_message();
			return array(
				'success' => false,
				'message' => $response->get_error_message(),
				'data'    => array(
					'host'       => 'api.sendgrid.com',
					'encryption' => 'https',
					'auth'       => false,
					'debug'      => $debug,
				),
			);
		}

		$code    = wp_remote_retrieve_response_code( $response );
		$debug[] = sprintf( 'Response: %d %s', $code, wp_remote_retrieve_response_message( $response ) );

		if ( 200 === $code ) {
			return array(
				'success' => true,
				'message' => __( 'SendGrid API credentials verified successfully.', 'mail-doctor' ),
				'data'    => array(
					'host'       => 'api.sendgrid.com',
					'encryption' => 'https',
					'auth'       => true,
					'debug'      => $debug,
				),
			);
		}

		$body = wp_remote_retrieve_body( $response );
		if ( $body ) {
			$debug[] = $body;
		}

		return array(
			'success' => false,
			'message' => __( 'SendGrid API request was rejected. Confirm the API key permissions.', 'mail-doctor' ),
			'data'    => array(
				'host'       => 'api.sendgrid.com',
				'encryption' => 'https',
				'auth'       => false,
				'debug'      => $debug,
			),
		);
	}

	/**
	 * Build personalization payload.
	 *
	 * @param array<string,array> $recipients Recipient buckets.
	 * @return array<string,array>
	 */
	protected static function build_personalization( array $recipients ): array {
		$personalization = array(
			'to' => self::format_addresses( $recipients['to'] ),
		);

		if ( ! empty( $recipients['cc'] ) ) {
			$personalization['cc'] = self::format_addresses( $recipients['cc'] );
		}

		if ( ! empty( $recipients['bcc'] ) ) {
			$personalization['bcc'] = self::format_addresses( $recipients['bcc'] );
		}

		return $personalization;
	}

	/**
	 * Format addresses for SendGrid schema.
	 *
	 * @param array<int,array<string,string>> $list Address list.
	 * @return array<int,array<string,string>>
	 */
	protected static function format_addresses( array $list ): array {
		$formatted = array();

		foreach ( $list as $address ) {
			if ( empty( $address['email'] ) ) {
				continue;
			}

			$item = array( 'email' => $address['email'] );
			if ( ! empty( $address['name'] ) ) {
				$item['name'] = $address['name'];
			}

			$formatted[] = $item;
		}

		return $formatted;
	}

	/**
	 * Format a single address map.
	 *
	 * @param array<string,string> $address Address data.
	 * @return array<string,string>
	 */
	protected static function format_address_map( array $address ): array {
		$map = array( 'email' => $address['email'] );
		if ( ! empty( $address['name'] ) ) {
			$map['name'] = $address['name'];
		}
		return $map;
	}

	/**
	 * Construct SendGrid content block.
	 *
	 * @param array<string,?string> $bodies Message bodies.
	 * @return array<int,array<string,string>>
	 */
	protected static function build_content( array $bodies ): array {
		$content = array();

		$plain = (string) $bodies['text'];
		if ( '' === $plain && null !== $bodies['html'] ) {
			$plain = wp_strip_all_tags( $bodies['html'] );
		}

		if ( '' !== $plain ) {
			$content[] = array(
				'type'  => 'text/plain',
				'value' => $plain,
			);
		}

		if ( null !== $bodies['html'] ) {
			$content[] = array(
				'type'  => 'text/html',
				'value' => (string) $bodies['html'],
			);
		}

		return $content;
	}

	/**
	 * Strip headers that are disallowed by the SendGrid API.
	 *
	 * @param array<string,string> $headers Normalized headers.
	 * @return array<string,string>
	 */
	protected static function filter_headers( array $headers ): array {
		$reserved = array(
			'authorization',
			'content-type',
			'content-length',
			'content-transfer-encoding',
			'date',
			'to',
			'from',
			'subject',
			'reply-to',
			'cc',
			'bcc',
			'host',
			'accept',
			'accept-encoding',
			'connection',
		);

		$allowed = array();

		foreach ( $headers as $name => $value ) {
			$header_name = strtolower( trim( $name ) );
			if ( '' === $header_name || in_array( $header_name, $reserved, true ) ) {
				continue;
			}

			if ( '' === trim( (string) $value ) ) {
				continue;
			}

			$allowed[ $name ] = $value;
		}

		return $allowed;
	}
}
