<?php
/**
 * A class that sends email through Amazon SES.
 *
 * @since 3.0.0
 */

declare( strict_types=1 );
namespace FeData\FastEvents\Mail\Provider;

defined( 'ABSPATH' ) || exit;

use Exception;
use FeData\FastEvents\Mail\AbstractMailer;
use FeData\FastEvents\Utilities\PluginUtil;
use stdClass;
use WP_Error;
use WP_REST_Request;
use WP_REST_Response;

/**
 * A class that sends email through Amazon SES.
 */
final class AmazonSesMailer extends AbstractMailer {

	/**
	 * Send mail using the Amazon SES API.
	 *
	 * @since 1.0
	 * @since 1.9 Added event_id and order_id to the message.
	 * @since 2.5.0 Set provider id.
	 *
	 * @param stdClass $order   Order information.
	 * @param string   $bcc     Emailaddress of BBC receiver.
	 * @param string   $subject Subject of the email.
	 * @param string   $msg     HTML mail message.
	 * @param int      $em_type 0=Normal mail, 1=Confirmation email.
	 * @throws Exception If mail can't be sent.
	 */
	protected function provider_send( stdClass $order, string $bcc, string $subject, string $msg, int $em_type ): void {
		$this->provider_id = 'amazon';

		// Create headers.
		$headers = array(
			'Content-Type' => 'application/x-www-form-urlencoded',
			'Host'         => 'email.' . $this->settings->mailer_property( 'region' ) . '.amazonaws.com',
			'X-Amz-Date'   => gmdate( 'Ymd\\THis\\Z' ),
		);

		// Is there a name?
		if ( ! empty( $order->name ) ) {
			$rcpt = $order->name . ' <' . $order->email . '>';
		} else {
			$rcpt = $order->email;
		}

		// Create the HTTP body.
		$vars = array(
			'Action'                           => 'SendEmail',
			'Source'                           => $this->get_sender_name( $order ) . ' <' . $this->get_sender_email( $order ) . '>',
			'Destination.ToAddresses.member.1' => $rcpt,
			'Message.Subject.Charset'          => 'UTF-8',
			'Message.Subject.Data'             => $subject,
			'Message.Body.Text.Charset'        => 'UTF-8',
			'Message.Body.Text.Data'           => wp_strip_all_tags( $msg ),
			'Message.Body.Html.Charset'        => 'UTF-8',
			'Message.Body.Html.Data'           => $msg,
		);
		if ( ! empty( $bcc ) ) {
			$vars['Destination.BccAddresses.member.1'] = $bcc;
		}
		$body = http_build_query( $vars );

		// Send the message.
		$response = wp_safe_remote_post(
			'https://email.' . $this->settings->mailer_property( 'region' ) . '.amazonaws.com',
			array(
				'user-agent' => PluginUtil::get_user_agent(),
				'timeout'    => self::CONNECTION_TIMEOUT,
				'headers'    => array_merge(
					$headers,
					array(
						'Authorization' => $this->amazon_ses_get_signature( 'POST', '/', '', $headers, $body ),
					)
				),
				'body'       => $body,
			)
		);

		// Check for errors.
		$this->check_email_provider_api_error( $response, $order, $em_type );
	}

	/**
	 * Signs a request made to the Amazon Query API (SES in this case).
	 *
	 * @param string $method       The method to use (i.e.: GET or POST).
	 * @param string $path         The path to call.
	 * @param string $query_string The query string of the call.
	 * @param array  $headers      An associative array with the headers.
	 * @param string $body         The request body payload.
	 *
	 * @return string The signature.
	 */
	private function amazon_ses_get_signature( string $method, string $path, string $query_string, array $headers, string $body ): string {
		$region       = $this->settings->mailer_property( 'region' );
		$secret_key   = $this->settings->mailer_property( 'secret_key' );
		$access_key   = $this->settings->mailer_property( 'access_key' );
		$service      = 'ses';
		$date         = wp_date( 'Ymd' );
		$signing      = 'aws4_request';
		$algorithm    = 'AWS4-HMAC-SHA256';
		$access_scope = $date . '/' . $region . '/' . $service . '/' . $signing;
		$header_names = array();

		$canonical_request  = $method . "\n";
		$canonical_request .= $path . "\n";
		$canonical_request .= $query_string . "\n";

		foreach ( $headers as $key => $value ) {
			$header_name        = trim( strtolower( $key ) );
			$canonical_request .= $header_name . ':' . trim( $value ) . "\n";
			$header_names[]     = $header_name;
		}

		$signed_headers = implode( ';', $header_names );

		$canonical_request .= "\n";
		$canonical_request .= $signed_headers . "\n";
		$canonical_request .= hash( 'sha256', $body );

		$string_to_sign  = $algorithm . "\n";
		$string_to_sign .= ( $headers['X-Amz-Date'] ?? gmdate( 'Ymd\\THis\\Z' ) ) . "\n";
		$string_to_sign .= $access_scope . "\n";
		$string_to_sign .= hash( 'sha256', $canonical_request );

		$hashed_date    = hash_hmac( 'sha256', $date, 'AWS4' . $secret_key, true );
		$hashed_region  = hash_hmac( 'sha256', $region, $hashed_date, true );
		$hashed_service = hash_hmac( 'sha256', $service, $hashed_region, true );
		$hashed_signing = hash_hmac( 'sha256', $signing, $hashed_service, true );

		$signature = hash_hmac( 'sha256', $string_to_sign, $hashed_signing );

		return $algorithm . ' Credential=' . $access_key . '/' . $access_scope . ', SignedHeaders=' . $signed_headers . ', Signature=' . $signature;
	}


	/**
	 * Extract the email_id used by Amazon SES.
	 *
	 * @since 2.5.0
	 *
	 * @param array|WP_Error $response API response from provider.
	 */
	protected function set_email_id( WP_Error|array $response ): void {
		$body           = wp_remote_retrieve_body( $response );
		$xml            = simplexml_load_string( $body );
		$ns             = $xml->getNamespaces( true );
		$this->email_id = (string) $xml->children( $ns[''] )->SendEmailResult->MessageId;
	}

	/**
	 * Process the Amazon SES webhook events.
	 *
	 * @since 2.4.0
	 * @since 2.5.0 All events are added to the email log, and error events are added to the errorlog as well.
	 *
	 * @param WP_REST_Request $data Request instance.
	 *
	 * @return WP_REST_Response | WP_Error
	 */
	public function webhook( WP_REST_Request $data ): WP_Error|WP_REST_Response {
		$this->provider_id = 'amazon';
		return new WP_Error( 'not_implemented', __( 'This Amazon SES implementation does not support webhooks.', 'fast-events' ) );
	}
}
