<?php

namespace AppBundle\Mail;

use AppBundle\Mail\Model\Mail;
use AppBundle\Mail\Mailer;
use Doctrine\ORM\EntityManagerInterface;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Flash\FlashBagInterface;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Templating\EngineInterface;
use Symfony\Component\Translation\TranslatorInterface;
use Vich\UploaderBundle\Templating\Helper\UploaderHelper;

/**
 * Send Mail object(s) using the SendinBlue API.
 * Log these actions (mail + SendinBlue response message).
 * Optionally displays a flash message after the success / failure.
 * 
 * @see https://apidocs.sendinblue.com/tutorial-sending-transactional-email/
 * @see https://apidocs.sendinblue.com/response/
 */
abstract class AbstractMailer
{
    /**
     * @var EntityManagerInterface
     */
    protected $em;

    /**
     * @var FlashBagInterface
     */
    private $flashBag;

    /**
     * @var LoggerInterface
     */
    private $logger;

    /**
     * @var Mailer
     */
    private $mailer;

    /**
     * @var array
     */
    private $parameters;

    /**
     * @var UrlGeneratorInterface
     */
    protected $router;

    /**
     * @var TranslatorInterface
     */
    protected $translator;

    /**
     * @var EngineInterface
     */
    private $templating;

    /**
     * @var UploaderHelper
     */
    private $vichUploader;

    public function __construct(
        LoggerInterface $logger,
        Mailer $mailer,
        SessionInterface $session,
        EngineInterface $templating,
        TranslatorInterface $translator,
        UploaderHelper $vichUploader,
        \Doctrine\ORM\EntityManager $em
    )
    {
        $this->flashBag = $session->getFlashBag();
        $this->logger = $logger;
        $this->mailer = $mailer;
        $this->templating = $templating;
        $this->translator = $translator;
        $this->vichUploader = $vichUploader;
        $this->em = $em;
    }

    /**
     * @param EntityManagerInterface $em
     */
    public function setEntityManager(EntityManagerInterface $em): void
    {
        $this->em = $em;
    }

    /**
     * @param array $parameters
     */
    public function setParameters(array $parameters): void
    {
        $this->parameters = $parameters;
    }

    /**
     * @param UrlGeneratorInterface $router
     */
    public function setRouter(UrlGeneratorInterface $router): void
    {
        $this->router = $router;
    }

    /**
     * 1. Prepare the mail data from the given Mail objects
     * 2. Send the mail(s) for each Mail object
     * 3. Log the mail(s) sent and the SendinBlue response message(s)
     * 4. [Optional] Add a Flash message about each mail sent
     *
     * @param mixed $mails A single Mail or an array of Mail
     * @param bool $displayFlashMessages Indicates if flash messages should be displayed after the mail have been send
     *
     * @return array Sendinblue responses for each given mail
     */
    public function send($mails, bool $displayFlashMessages = false): array
    {
        $mailRedirect = $this->parameters['redirect'] ? [$this->parameters['redirect'] => null] : null;
        $responses = [];
        $schemeAndHttpHost = (Request::createFromGlobals())->getSchemeAndHttpHost();

        if (!is_array($mails)) {
            $mails = [$mails];
        }

        foreach ($mails as $mail) {
            if ($mail instanceof Mail) {
                // Prepare data
                $fromAddress = array_keys($mail->getFrom())[0];
                $to = $mailRedirect ?? $mail->getTo();

                $message = [
                    'from' => [$fromAddress, $mail->getFrom()[$fromAddress]],
                    'to' => $to,
                    'subject' => $mail->getSubject(),
                    'html' => $mail->getHtml(),
                    'headers' => [
                        'Content-Type' => $mail->getContentType() . '; charset=' . $mail->getCharset()
                    ],
                ];

                if (!empty($mail->getCc())) {
                    $message['cc'] = $mailRedirect ?? $mail->getCc();
                }

                if (!empty($mail->getText())) {
                    $message['text'] = $mail->getText();
                }

                if (!$mail->getAttachments()->isEmpty()) {
                    $message['attachment'] = [];

                    foreach ($mail->getAttachments() as $document) {
                        $message['attachment'][] = $schemeAndHttpHost . $this->vichUploader->asset($document, 'file');
                    }
                }

                // Send data and catch the response
                $response = $this->mailer->send_email($message);
               
                $responses[] = $response;

                // @see https://apidocs.sendinblue.com/fr/response/
                if ('failure' === $response['code'] || 'success' === $response['code']) {
                    if ('failure' === $response['code']) { // No mail have been send :(
                        $flashBagType = 'error';
                        $logLevel = LogLevel::ERROR;
                        $translationKey = 'notice.mail.sent.failure';
                    } else { // All the mails have been send :D
                        $flashBagType = 'success';
                        $logLevel = LogLevel::INFO;
                        $translationKey = 'notice.mail.sent.success';
                    }

                    // Log the API response
                    $this->logger->log($logLevel, 'Mail ' . $response['code'], array_merge([
                        'description' => $mail->getDescription(),
                        'response' => $response['message'],
                    ], $message));

                    // Handle flash messages
                    if ($displayFlashMessages) {
                        $this->flashBag->add($flashBagType, $this->translator->transchoice(
                            $translationKey,
                            count($to),
                            ['%description%' => $mail->getDescription()],
                            'notice'
                        ));
                    }
                }
            }
        }

        return $responses;
    }

    /**
     * @param string $name
     * @return string
     * @throws \Exception
     */
    protected function getParameter(string $name): string
    {
        if (!isset($this->parameters[$name])) {
            throw new \Exception(
                sprintf('Cannot retrieve the %s parameter. Available parameters: %s', $name, join(', ', array_keys($this->parameters)))
            );
        }

        return $this->parameters[$name];
    }

    /**
     * Returns a rendered view.
     *
     * @param string $view       The view name
     * @param array  $parameters An array of parameters to pass to the view
     *
     * @return string The rendered view
     */
    protected function renderView(string $view, array $parameters = []): string
    {
        return $this->templating->render($view, $parameters);
    }
}
