<?php

namespace WPMailSMTP\Pro\Providers\Zoho;

use WPMailSMTP\Admin\DebugEvents\DebugEvents;
use WPMailSMTP\ConnectionInterface;
use WPMailSMTP\Helpers\Helpers;
use WPMailSMTP\MailCatcher;
use WPMailSMTP\Providers\MailerAbstract;
use WPMailSMTP\Options as PluginOptions;
use WPMailSMTP\WP;
use WPMailSMTP\Pro\Providers\Zoho\Auth\Zoho as ZohoAuth;

/**
 * Class Mailer implements Zoho Mail API functionality.
 *
 * @see https://www.zoho.com/mail/help/api/post-send-email-attachment.html
 *
 * @since 2.3.0
 */
class Mailer extends MailerAbstract {

	/**
	 * Which response code from HTTP provider is considered to be successful?
	 *
	 * @since 2.3.0
	 *
	 * @var int
	 */
	protected $email_sent_code = 200;

	/**
	 * URL to make an API request to.
	 *
	 * This is only the root URL, the full URL will look something like this:
	 * https://mail.zoho.<domain>/api/accounts/<accountId>/messages
	 *
	 * The domain and accountId will be added in constructor so it will look something like:
	 * https://mail.zoho.com/api/accounts/3231jq...9ej2119/
	 *
	 * The actual endpoint eg. `messages` will be added when needed.
	 *
	 * @since 2.3.0
	 * @since 4.2.0 Will be defined in the constructor based on the domain.
	 *
	 * @var string
	 */
	protected $root_url = '';

	/**
	 * Mailer constructor.
	 *
	 * @since 2.3.0
	 *
	 * @param MailCatcher         $phpmailer  The MailCatcher object.
	 * @param ConnectionInterface $connection The Connection object.
	 */
	public function __construct( $phpmailer, $connection = null ) {

		// Init the client that checks tokens and re-saves them if needed.
		new Auth( $connection );

		// We want to prefill everything from \WPMailSMTP\MailCatcher class, which extends \PHPMailer.
		parent::__construct( $phpmailer, $connection );

		$token        = $this->connection_options->get( $this->mailer, 'access_token' );
		$domain       = $this->connection_options->get( $this->mailer, 'domain' );
		$user_details = $this->connection_options->get( $this->mailer, 'user_details' );
		$account_id   = ! empty( $user_details['account_id'] ) ? $user_details['account_id'] : '';

		// Define the root URL.
		$this->root_url  = isset( ZohoAuth::$mail_endpoints[ $domain ] ) ? ZohoAuth::$mail_endpoints[ $domain ] : ZohoAuth::$mail_endpoints['com'];
		$this->root_url .= $domain . '/api/accounts/' . $account_id . '/';

		if ( ! empty( $token['access_token'] ) ) {
			$this->set_header( 'Authorization', 'Zoho-oauthtoken ' . $token['access_token'] );
		}
		$this->set_header( 'Content-Type', 'application/json' );

		$this->process_phpmailer( $phpmailer );
	}

	/**
	 * PhpMailer generates certain headers, including our custom.
	 * We want to preserve them when sending an email.
	 *
	 * Zoho does not support custom email headers.
	 * So we do nothing.
	 *
	 * @since 2.3.0
	 *
	 * @param array $headers The headers to set.
	 */
	public function set_headers( $headers ) {}

	/**
	 * Define the from email and name.
	 *
	 * It doesn't support random email, should be the same as used for OAuth authentication/connection.
	 *
	 * @since 2.3.0
	 *
	 * @param string $email Not used.
	 * @param string $name  The from name.
	 */
	public function set_from( $email, $name = '' ) {

		$sender = $this->connection_options->get( $this->mailer, 'user_details' );
		$from   = sprintf(
			'"%1$s" <%2$s>',
			wp_slash( $name ),
			isset( $sender['email'] ) ? $sender['email'] : ''
		);

		$this->set_body_param(
			[
				'fromAddress' => $from,
			]
		);
	}

	/**
	 * Define the CC/BCC/TO email and name lists.
	 *
	 * @since 2.3.0
	 *
	 * @param array $recipients The multi dimensional array of all to/cc/bcc email addresses and names.
	 */
	public function set_recipients( $recipients ) {

		if ( empty( $recipients ) ) {
			return;
		}

		// Allow for now only these recipient types.
		$default = [ 'to', 'cc', 'bcc' ];
		$data    = [];

		foreach ( $recipients as $type => $emails ) {
			if (
				! in_array( $type, $default, true ) ||
				empty( $emails ) ||
				! is_array( $emails )
			) {
				continue;
			}

			$type_id = $type . 'Address';

			$data[ $type_id ] = [];

			// Iterate over all emails for each type.
			// There might be multiple to/cc/bcc emails.
			foreach ( $emails as $email ) {
				$addr = isset( $email[0] ) ? $email[0] : false;
				$name = isset( $email[1] ) ? $email[1] : false;

				if ( ! filter_var( $addr, FILTER_VALIDATE_EMAIL ) ) {
					continue;
				}

				if ( ! empty( $name ) ) {
					$addr = sprintf( '%1$s <%2$s>', $name, $addr );
				}

				$data[ $type_id ][] = $addr;
			}
		}

		if ( ! empty( $data ) ) {
			foreach ( $data as $type_id => $type_data ) {
				$this->set_body_param(
					[
						$type_id => implode( ',', $type_data ),
					]
				);
			}
		}
	}

	/**
	 * Set the email content.
	 * Zoho supports only HTML email content, and they create a multipart email with
	 * automatic HTML stripping for the plain text version.
	 *
	 * @since 2.3.0
	 *
	 * @param array|string $content String when text/plain, array otherwise.
	 */
	public function set_content( $content ) {

		if ( empty( $content ) ) {
			return;
		}

		if ( is_array( $content ) ) {
			if (
				! isset( $content['text'] ) ||
				! isset( $content['html'] )
			) {
				return;
			}

			$content = ! empty( $content['html'] ) ? $content['html'] : $content['text'];
		} else {
			$content = nl2br( $content );
		}

		$this->set_body_param(
			[
				'content' => $content,
			]
		);
	}

	/**
	 * Set Reply-To part of the message.
	 *
	 * Zoho requires the users to verify the reply-to email address.
	 * Only one reply-to email address is allowed, so we take the first one.
	 *
	 * @since 2.3.0
	 * @since 2.9.0 Zoho does not allow dynamic reply-to addresses, so we removed the support for them. GH #645.
	 *
	 * @param array $reply_to The multidimensional array of email addresses and names that
	 *                        should be used for the reply field.
	 */
	public function set_reply_to( $reply_to ) {}

	/**
	 * Set the return path.
	 *
	 * Zoho doesn't support return_path params.
	 * So we do nothing.
	 *
	 * @since 2.3.0
	 *
	 * @param string $from_email The email address that should be used for the return path.
	 */
	public function set_return_path( $from_email ) {}

	/**
	 * Add attachments to the body.
	 * Zoho requires the attachments to be uploaded via an endpoint prior to sending the email.
	 * The collected upload data has to be then added to the email sending request.
	 *
	 * @since 2.3.0
	 *
	 * @see https://www.zoho.com/mail/help/api/post-send-email-attachment.html
	 * @see https://www.zoho.com/mail/help/api/post-upload-attachments.html
	 *
	 * @param array $attachments Array of attachments.
	 */
	public function set_attachments( $attachments ) {

		if ( empty( $attachments ) ) {
			return;
		}

		$data = $this->prepare_attachments( $attachments );

		if ( ! empty( $data ) ) {
			$this->set_body_param(
				[
					'attachments' => $data,
				]
			);
		}
	}

	/**
	 * Prepare attachments data for Zoho API.
	 *
	 * @since 3.1.0
	 *
	 * @param array $attachments Array of attachments.
	 *
	 * @return array
	 */
	protected function prepare_attachments( $attachments ) {

		$data = [];

		// Prepare request headers.
		$headers                 = $this->get_headers();
		$headers['Content-Type'] = 'application/octet-stream';

		foreach ( $attachments as $attachment ) {
			$file = $this->get_attachment_file_content( $attachment );

			if ( $file === false ) {
				continue;
			}

			// Upload the attachment via Zoho API.
			$url = add_query_arg(
				'fileName',
				$this->get_attachment_file_name( $attachment ),
				$this->root_url . 'messages/attachments'
			);

			$params = [
				'headers' => $headers,
				'body'    => $file,
			];

			$response = wp_safe_remote_post( $url, $params );

			if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
				continue;
			}

			$body = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( ! empty( $body['data'] ) ) {
				$data[] = $body['data'];
			}
		}

		return $data;
	}

	/**
	 * Redefine the way email body is returned.
	 * Zoho API needs JSON object.
	 *
	 * @since 2.3.0
	 *
	 * @return string
	 */
	public function get_body() {

		return wp_json_encode( parent::get_body() );
	}

	/**
	 * Send the email using Zoho Mail API.
	 *
	 * @since 2.3.0
	 */
	public function send() {

		$params = PluginOptions::array_merge_recursive(
			$this->get_default_params(),
			[
				'headers' => $this->get_headers(),
				'body'    => $this->get_body(),
			]
		);

		/*
		 * Right now Zoho doesn't allow to redefine From and Sender email headers.
		 * It always uses the email address that was used to connect to its API.
		 * With code below we are making sure that Email Log archive and single Email Log
		 * have the save value for From email header.
		 */
		$sender = $this->connection_options->get( $this->mailer, 'user_details' );

		if ( ! empty( $sender['email'] ) ) {
			$this->phpmailer->From   = $sender['email'];
			$this->phpmailer->Sender = $sender['email'];
		}

		$response = wp_safe_remote_post( $this->root_url . 'messages', $params );

		DebugEvents::add_debug(
			esc_html__( 'An email request was sent to the Zoho Mail API.', 'wp-mail-smtp-pro' )
		);

		$this->process_response( $response );
	}

	/**
	 * Process Zoho API response with a helpful error.
	 *
	 * @since 2.3.0
	 *
	 * @return string
	 */
	public function get_response_error() {

		$error_text[] = $this->error_message;

		if ( ! empty( $this->response ) ) {
			$body = wp_remote_retrieve_body( $this->response );

			if ( ! empty( $body->data->errorCode ) || ! empty( $body->data->moreInfo ) ) {
				$message     = ! empty( $body->status->description ) ? $body->status->description : '';
				$code        = ! empty( $body->status->code ) ? $body->status->code : '';
				$description = ! empty( $body->data->errorCode ) ? $body->data->errorCode : $body->data->moreInfo;

				$error_text[] = Helpers::format_error_message( $message, $code, $description );
			} else {
				$error_text[] = WP::wp_remote_get_response_error_message( $this->response );
			}
		}

		return implode( WP::EOL, array_map( 'esc_textarea', array_filter( $error_text ) ) );
	}

	/**
	 * Get mailer debug information, that is helpful during support.
	 *
	 * @since 2.3.0
	 *
	 * @return string
	 */
	public function get_debug_info() {

		$mg_text = [];

		$auth = new Auth( $this->connection );

		$mg_text[] = '<strong>Domain:</strong> ' . (string) $this->connection_options->get( $this->mailer, 'domain' );
		$mg_text[] = '<strong>Client ID/Secret:</strong> ' . ( $auth->is_clients_saved() ? 'Yes' : 'No' );
		$mg_text[] = '<strong>Tokens:</strong> ' . ( ! $auth->is_auth_required() ? 'Yes' : 'No' );

		return implode( '<br>', $mg_text );
	}

	/**
	 * Whether the mailer has all its settings correctly set up and saved.
	 *
	 * @since 2.3.0
	 *
	 * @return bool
	 */
	public function is_mailer_complete() {

		if ( ! $this->is_php_compatible() ) {
			return false;
		}

		$auth = new Auth( $this->connection );

		if (
			$auth->is_clients_saved() &&
			! $auth->is_auth_required() &&
			! empty( $this->connection_options->get( $this->mailer, 'domain' ) )
		) {
			return true;
		}

		return false;
	}
}
