<?php

namespace MailPoet\Mailer;

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


use MailPoet\DI\ContainerWrapper;
use MailPoet\Mailer\Methods\AmazonSES;
use MailPoet\Mailer\Methods\ErrorMappers\AmazonSESMapper;
use MailPoet\Mailer\Methods\ErrorMappers\MailPoetMapper;
use MailPoet\Mailer\Methods\ErrorMappers\PHPMailMapper;
use MailPoet\Mailer\Methods\ErrorMappers\SendGridMapper;
use MailPoet\Mailer\Methods\ErrorMappers\SMTPMapper;
use MailPoet\Mailer\Methods\MailPoet;
use MailPoet\Mailer\Methods\PHPMail;
use MailPoet\Mailer\Methods\SendGrid;
use MailPoet\Mailer\Methods\SMTP;
use MailPoet\Services\AuthorizedEmailsController;
use MailPoet\Settings\SettingsController;

class Mailer {
  public $mailerConfig;
  public $sender;
  public $replyTo;
  public $returnPath;
  public $mailerInstance;
  /** @var SettingsController */
  private $settings;
  const MAILER_CONFIG_SETTING_NAME = 'mta';
  const SENDING_LIMIT_INTERVAL_MULTIPLIER = 60;
  const METHOD_MAILPOET = 'MailPoet';
  const METHOD_AMAZONSES = 'AmazonSES';
  const METHOD_SENDGRID = 'SendGrid';
  const METHOD_PHPMAIL = 'PHPMail';
  const METHOD_SMTP = 'SMTP';

  public function __construct(SettingsController $settings = null) {
    if (!$settings) {
      $settings = SettingsController::getInstance();
    }
    $this->settings = $settings;
  }

  public function init($mailer = false, $sender = false, $replyTo = false, $returnPath = false) {
    $this->mailerConfig = $this->getMailerConfig($mailer);
    $this->sender = $this->getSenderNameAndAddress($sender);
    $this->replyTo = $this->getReplyToNameAndAddress($replyTo);
    $this->returnPath = $this->getReturnPathAddress($returnPath);
    $this->mailerInstance = $this->buildMailer();
  }

  public function send($newsletter, $subscriber, $extraParams = []) {
    if (!$this->mailerInstance) {
      $this->init();
    }
    $subscriber = $this->formatSubscriberNameAndEmailAddress($subscriber);
    return $this->mailerInstance->send($newsletter, $subscriber, $extraParams);
  }

  private function buildMailer() {
    switch ($this->mailerConfig['method']) {
      case self::METHOD_AMAZONSES:
        $mailerInstance = new AmazonSES(
          $this->mailerConfig['region'],
          $this->mailerConfig['access_key'],
          $this->mailerConfig['secret_key'],
          $this->sender,
          $this->replyTo,
          $this->returnPath,
          new AmazonSESMapper()
        );
        break;
      case self::METHOD_MAILPOET:
        $mailerInstance = new MailPoet(
          $this->mailerConfig['mailpoet_api_key'],
          $this->sender,
          $this->replyTo,
          new MailPoetMapper(),
          ContainerWrapper::getInstance()->get(AuthorizedEmailsController::class)
        );
        break;
      case self::METHOD_SENDGRID:
        $mailerInstance = new SendGrid(
          $this->mailerConfig['api_key'],
          $this->sender,
          $this->replyTo,
          new SendGridMapper()
        );
        break;
      case self::METHOD_PHPMAIL:
        $mailerInstance = new PHPMail(
          $this->sender,
          $this->replyTo,
          $this->returnPath,
          new PHPMailMapper()
        );
        break;
      case self::METHOD_SMTP:
        $mailerInstance = new SMTP(
          $this->mailerConfig['host'],
          $this->mailerConfig['port'],
          $this->mailerConfig['authentication'],
          $this->mailerConfig['login'],
          $this->mailerConfig['password'],
          $this->mailerConfig['encryption'],
          $this->sender,
          $this->replyTo,
          $this->returnPath,
          new SMTPMapper()
        );
        break;
      default:
        throw new \Exception(__('Mailing method does not exist.', 'mailpoet'));
    }
    return $mailerInstance;
  }

  private function getMailerConfig($mailer = false) {
    if (!$mailer) {
      $mailer = $this->settings->get(self::MAILER_CONFIG_SETTING_NAME);
      if (!$mailer || !isset($mailer['method'])) throw new \Exception(__('Mailer is not configured.', 'mailpoet'));
    }
    return $mailer;
  }

  private function getSenderNameAndAddress($sender = false) {
    if (empty($sender)) {
      $sender = $this->settings->get('sender', []);
      if (empty($sender['address'])) throw new \Exception(__('Sender name and email are not configured.', 'mailpoet'));
    }
    $fromName = $this->encodeAddressNamePart($sender['name']);
    return [
      'from_name' => $fromName,
      'from_email' => $sender['address'],
      'from_name_email' => sprintf('%s <%s>', $fromName, $sender['address']),
    ];
  }

  public function getReplyToNameAndAddress($replyTo = []) {
    if (!$replyTo) {
      $replyTo = $this->settings->get('reply_to');
      $replyTo['name'] = (!empty($replyTo['name'])) ?
        $replyTo['name'] :
        $this->sender['from_name'];
      $replyTo['address'] = (!empty($replyTo['address'])) ?
        $replyTo['address'] :
        $this->sender['from_email'];
    }
    if (empty($replyTo['address'])) {
      $replyTo['address'] = $this->sender['from_email'];
    }
    $replyToName = $this->encodeAddressNamePart($replyTo['name']);
    return [
      'reply_to_name' => $replyToName,
      'reply_to_email' => $replyTo['address'],
      'reply_to_name_email' => sprintf('%s <%s>', $replyToName, $replyTo['address']),
    ];
  }

  public function getReturnPathAddress($returnPath) {
    return ($returnPath) ?
      $returnPath :
      $this->settings->get('bounce.address');
  }

  /**
   * @param  \MailPoet\Models\Subscriber|array|string $subscriber
   */
  public function formatSubscriberNameAndEmailAddress($subscriber) {
    $subscriber = (is_object($subscriber)) ? $subscriber->asArray() : $subscriber;
    if (!is_array($subscriber)) return $subscriber;
    if (isset($subscriber['address'])) $subscriber['email'] = $subscriber['address'];
    $firstName = (isset($subscriber['first_name'])) ? $subscriber['first_name'] : '';
    $lastName = (isset($subscriber['last_name'])) ? $subscriber['last_name'] : '';
    $fullName = (isset($subscriber['full_name'])) ? $subscriber['full_name'] : null;
    if (!$firstName && !$lastName && !$fullName) return $subscriber['email'];
    $fullName = is_null($fullName) ? sprintf('%s %s', $firstName, $lastName) : $fullName;
    $fullName = trim(preg_replace('!\s\s+!', ' ', $fullName));
    $fullName = $this->encodeAddressNamePart($fullName);
    $subscriber = sprintf(
      '%s <%s>',
      $fullName,
      $subscriber['email']
    );
    return $subscriber;
  }

  public function encodeAddressNamePart($name) {
    if (mb_detect_encoding($name) === 'ASCII') return $name;
    // encode non-ASCII string as per RFC 2047 (https://www.ietf.org/rfc/rfc2047.txt)
    return sprintf('=?utf-8?B?%s?=', base64_encode($name));
  }

  public static function formatMailerErrorResult(MailerError $error) {
    return [
      'response' => false,
      'error' => $error,
    ];
  }

  public static function formatMailerSendSuccessResult() {
    return [
      'response' => true,
    ];
  }
}
