<?php
/**
 * Amazon SES REST transport.
 *
 * @package MailDoctor
 */

namespace MailDoctor\Transports;

use PHPMailer\PHPMailer\PHPMailer;
use WP_Error;

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

/**
 * Handles sending mail via Amazon SES v2 API.
 */
class Ses {
	/**
	 * Send message via SES.
	 *
	 * @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 ) {
		$access_key = trim( (string) ( $settings['ses_api_key'] ?? '' ) );
		$secret_key = (string) ( $settings['ses_api_secret'] ?? '' );
		$region     = trim( (string) ( $settings['ses_api_region'] ?? 'us-east-1' ) );
		$token      = trim( (string) ( $settings['ses_api_session_token'] ?? '' ) );

		if ( '' === $access_key || '' === $secret_key ) {
			return new WP_Error( 'mail_doctor_ses_missing_credentials', __( 'Amazon SES access key and secret are required.', 'mail-doctor' ) );
		}

		$recipients = Utils::recipients_from_args( $args );
		if ( empty( $recipients['to'] ) && empty( $recipients['cc'] ) && empty( $recipients['bcc'] ) ) {
			return new WP_Error( 'mail_doctor_ses_missing_recipients', __( 'Amazon SES requires at least one recipient.', 'mail-doctor' ) );
		}

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

		$mime = self::build_raw_message( $args, $sender, $recipients );
		if ( is_wp_error( $mime ) ) {
			return $mime;
		}

		$destination = array(
			'ToAddresses'  => self::extract_emails( $recipients['to'] ),
			'CcAddresses'  => self::extract_emails( $recipients['cc'] ),
			'BccAddresses' => self::extract_emails( $recipients['bcc'] ),
		);

		$destination = array_filter(
			$destination,
			static function ( $value ) {
				return ! empty( $value );
			}
		);

		$payload = array(
			'FromEmailAddress' => Utils::format_address( $sender ),
			'Content'          => array(
				'Raw' => array(
					'Data' => base64_encode( $mime ),
				),
			),
		);

		if ( ! empty( $destination ) ) {
			$payload['Destination'] = $destination;
		}

		if ( ! empty( $recipients['reply_to'] ) ) {
			$payload['ReplyToAddresses'] = array( Utils::format_address( $recipients['reply_to'] ) );
		}

		$response = self::signed_request(
			'POST',
			$region,
			'/v2/email/outbound-emails',
			wp_json_encode( $payload ),
			array( 'Content-Type' => 'application/json' ),
			$access_key,
			$secret_key,
			$token
		);

		if ( is_wp_error( $response ) ) {
			return $response;
		}

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

		if ( $code < 200 || $code >= 300 ) {
			$message = __( 'Amazon SES API request failed.', 'mail-doctor' );
			if ( $body ) {
				$json = json_decode( $body, true );
				if ( isset( $json['message'] ) ) {
					$message = (string) $json['message'];
				}
			}

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

		$json          = json_decode( $body, true );
		$provider_code = isset( $json['MessageId'] ) ? (string) $json['MessageId'] : wp_remote_retrieve_header( $response, 'x-amzn-requestid' );

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

	/**
	 * Verify SES credentials.
	 *
	 * @param array<string,mixed> $settings Plugin settings.
	 * @return array<string,mixed>
	 */
	public static function check_connection( array $settings ): array {
		$access_key = trim( (string) ( $settings['ses_api_key'] ?? '' ) );
		$secret_key = (string) ( $settings['ses_api_secret'] ?? '' );
		$region     = trim( (string) ( $settings['ses_api_region'] ?? 'us-east-1' ) );
		$token      = trim( (string) ( $settings['ses_api_session_token'] ?? '' ) );

		$host = sprintf( 'email.%s.amazonaws.com', $region ? $region : 'us-east-1' );

		if ( '' === $access_key || '' === $secret_key ) {
			return array(
				'success' => false,
				'message' => __( 'Amazon SES credentials are missing.', 'mail-doctor' ),
				'data'    => array(
					'host'       => $host,
					'encryption' => 'https',
					'auth'       => false,
					'debug'      => array(),
				),
			);
		}

		$response = self::signed_request(
			'GET',
			$region,
			'/v2/email/account',
			'',
			array(),
			$access_key,
			$secret_key,
			$token
		);

		$debug = array( sprintf( 'GET %s', sprintf( 'https://%s/v2/email/account', $host ) ) );

		if ( is_wp_error( $response ) ) {
			$debug[] = $response->get_error_message();
			return array(
				'success' => false,
				'message' => $response->get_error_message(),
				'data'    => array(
					'host'       => $host,
					'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' => __( 'Amazon SES credentials verified successfully.', 'mail-doctor' ),
				'data'    => array(
					'host'       => $host,
					'encryption' => 'https',
					'auth'       => true,
					'debug'      => $debug,
				),
			);
		}

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

		return array(
			'success' => false,
			'message' => __( 'Amazon SES denied the credential check. Confirm IAM permissions and region.', 'mail-doctor' ),
			'data'    => array(
				'host'       => $host,
				'encryption' => 'https',
				'auth'       => false,
				'debug'      => $debug,
			),
		);
	}

	/**
	 * Build raw MIME message using PHPMailer.
	 *
	 * @param array<string,mixed>  $args       Mail arguments.
	 * @param array<string,string> $sender     Sender details.
	 * @param array<string,array>  $recipients Recipient buckets.
	 * @return string|WP_Error
	 */
	protected static function build_raw_message( array $args, array $sender, array $recipients ) {
		\MailDoctor\Mailer::ensure_phpmailer_loaded();

		try {
			$mailer          = new PHPMailer( true );
			$mailer->CharSet = 'UTF-8';
			$mailer->setFrom( $sender['email'], $sender['name'], false );

			foreach ( $recipients['to'] as $address ) {
				$mailer->addAddress( $address['email'], $address['name'] ?? '' );
			}

			foreach ( $recipients['cc'] as $address ) {
				$mailer->addCC( $address['email'], $address['name'] ?? '' );
			}

			foreach ( $recipients['bcc'] as $address ) {
				$mailer->addBCC( $address['email'], $address['name'] ?? '' );
			}

			if ( ! empty( $recipients['reply_to'] ) ) {
				$mailer->addReplyTo( $recipients['reply_to']['email'], $recipients['reply_to']['name'] ?? '' );
			}

			$bodies = Utils::message_bodies( $args );
			if ( null !== $bodies['html'] ) {
				$mailer->isHTML( true );
				$mailer->Body    = (string) $bodies['html'];
				$mailer->AltBody = (string) $bodies['text'];
			} else {
				$mailer->Body = (string) $bodies['text'];
			}

			$mailer->Subject = (string) ( $args['subject'] ?? '' );

			$headers_map = Utils::headers_to_map( $args['headers'] ?? array() );
			foreach ( $headers_map as $name => $value ) {
				$mailer->addCustomHeader( $name, $value );
			}

			foreach ( (array) ( $args['attachments'] ?? array() ) as $attachment ) {
				$path = self::normalize_attachment_path( $attachment );
				if ( '' !== $path && file_exists( $path ) ) {
					$mailer->addAttachment( $path );
				}
			}

			$mailer->preSend();
			return $mailer->getSentMIMEMessage();
		} catch ( \Exception $exception ) {
			return new WP_Error( 'mail_doctor_ses_mime_error', $exception->getMessage() );
		}
	}



	/**
	 * Normalize attachment path input.
	 *
	 * @param mixed $attachment Attachment entry.
	 * @return string
	 */
	protected static function normalize_attachment_path( $attachment ): string {
		if ( is_array( $attachment ) ) {
			if ( isset( $attachment['file'] ) ) {
				return wp_normalize_path( (string) $attachment['file'] );
			}
			if ( isset( $attachment['path'] ) ) {
				return wp_normalize_path( (string) $attachment['path'] );
			}
		}

		return wp_normalize_path( (string) $attachment );
	}

	/**
	 * Extract email addresses from recipient list.
	 *
	 * @param array<int,array<string,string>> $addresses Addresses.
	 * @return array<int,string>
	 */
	protected static function extract_emails( array $addresses ): array {
		return array_values(
			array_unique(
				array_filter(
					array_map(
						static function ( $address ) {
							return $address['email'] ?? '';
						},
						$addresses
					)
				)
			)
		);
	}

	/**
	 * Perform signed AWS request.
	 *
	 * @param string               $method  HTTP method.
	 * @param string               $region  AWS region.
	 * @param string               $path    Request path.
	 * @param string               $body    Payload body.
	 * @param array<string,string> $headers Additional headers.
	 * @param string               $access_key Access key.
	 * @param string               $secret_key Secret key.
	 * @param string               $session_token Session token.
	 * @return array|WP_Error
	 */
	protected static function signed_request( string $method, string $region, string $path, string $body, array $headers, string $access_key, string $secret_key, string $session_token = '' ) {
		$endpoint = sprintf( 'https://email.%s.amazonaws.com', $region ? $region : 'us-east-1' );
		$url      = $endpoint . $path;

		$parsed = wp_parse_url( $url );
		$host   = $parsed['host'];
		$uri    = isset( $parsed['path'] ) ? $parsed['path'] : '/';
		$query  = isset( $parsed['query'] ) ? $parsed['query'] : '';

		$payload_hash = hash( 'sha256', $body );
		$amz_date     = gmdate( 'Ymd\THis\Z' );
		$date_stamp   = gmdate( 'Ymd' );

		$canonical_map = array(
			'host'                 => strtolower( $host ),
			'x-amz-content-sha256' => $payload_hash,
			'x-amz-date'           => $amz_date,
		);

		if ( '' !== $session_token ) {
			$canonical_map['x-amz-security-token'] = $session_token;
		}

		foreach ( $headers as $name => $value ) {
			$canonical_map[ strtolower( $name ) ] = trim( $value );
		}

		ksort( $canonical_map );

		$canonical_headers = '';
		$signed_headers    = array();

		foreach ( $canonical_map as $name => $value ) {
			$canonical_headers .= sprintf( '%s:%s\n', $name, $value );
			$signed_headers[]   = $name;
		}

		$canonical_request = implode(
			"\n",
			array(
				$method,
				$uri,
				$query,
				$canonical_headers,
				implode( ';', $signed_headers ),
				$payload_hash,
			)
		);

		$scope          = sprintf( '%s/%s/%s/aws4_request', $date_stamp, $region, 'ses' );
		$string_to_sign = implode(
			"\n",
			array(
				'AWS4-HMAC-SHA256',
				$amz_date,
				$scope,
				hash( 'sha256', $canonical_request ),
			)
		);

		$signing_key = self::build_signing_key( $secret_key, $date_stamp, $region, 'ses' );
		$signature   = hash_hmac( 'sha256', $string_to_sign, $signing_key );

		$authorization = sprintf( 'AWS4-HMAC-SHA256 Credential=%s/%s, SignedHeaders=%s, Signature=%s', $access_key, $scope, implode( ';', $signed_headers ), $signature );

		$request_headers = array_merge(
			$headers,
			array(
				'Authorization'        => $authorization,
				'X-Amz-Date'           => $amz_date,
				'X-Amz-Content-Sha256' => $payload_hash,
			)
		);

		if ( '' !== $session_token ) {
			$request_headers['X-Amz-Security-Token'] = $session_token;
		}

		$args = array(
			'method'  => $method,
			'timeout' => 30,
			'headers' => $request_headers,
		);

		if ( 'POST' === $method || 'PUT' === $method ) {
			$args['body'] = $body;
		}

		$response = wp_remote_request( $url, $args );

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

		return $response;
	}

	/**
	 * Build signing key for AWS SigV4.
	 *
	 * @param string $secret_key Secret key.
	 * @param string $date_stamp Date stamp.
	 * @param string $region Region.
	 * @param string $service Service name.
	 * @return string
	 */
	protected static function build_signing_key( string $secret_key, string $date_stamp, string $region, string $service ): string {
		$k_date    = hash_hmac( 'sha256', $date_stamp, 'AWS4' . $secret_key, true );
		$k_region  = hash_hmac( 'sha256', $region, $k_date, true );
		$k_service = hash_hmac( 'sha256', $service, $k_region, true );

		return hash_hmac( 'sha256', 'aws4_request', $k_service, true );
	}
}
