<?php

namespace Velis\Notification;

use DomainException;
use Exception;
use Psr\SimpleCache\InvalidArgumentException;
use Velis\App;
use Velis\Bpm\Email\Account;
use Velis\Filesystem\FilesystemInterface;
use Velis\Lang;
use Velis\Mail\Mail;
use Velis\Mail\Transport\AwsSes;
use Velis\Model\DataObject\NoColumnsException;
use Velis\Model\File;
use Velis\Notification;

class MailNotification
{
    protected Notification $baseNotification;
    protected ?Account $account = null;
    protected MailRecipients $recipients;
    protected MailRenderer $renderer;
    protected array $additionalHeaders;
    protected ?string $replyTo;
    protected bool $force;
    protected bool $useQueue;
    protected string $queueMode;
    protected bool $attachmentsAsLink = false;
    protected ?Log $log = null;

    /**
     * Files size in MB
     */
    protected int $maxFilesSize;


    public function __construct(
        Notification $baseNotification,
        MailRenderer $renderer,
        MailRecipients $recipients,
        ?Account $account = null,
        ?array $additionalHeaders = [],
        ?bool $useQueue = false,
        ?string $queueMode = Notification::QUEUE_CRON,
        ?bool $force = false,
        ?bool $attachmentsAsLink = false,
        ?string $replyTo = null,
        ?int $maxFilesSize = 5
    ) {
        $this->baseNotification = $baseNotification;
        $this->renderer = $renderer;
        $this->recipients = $recipients;
        $this->additionalHeaders = $additionalHeaders;
        $this->useQueue = $useQueue;
        $this->queueMode = $queueMode;
        $this->force = $force;
        $this->attachmentsAsLink = $attachmentsAsLink;
        $this->replyTo = $replyTo;
        $this->maxFilesSize = $maxFilesSize;

        if ($account) {
            $this->setAccount($account);
        }
    }


    /**
     * @return Log|null
     */
    public function getLog(): ?Log
    {
        return $this->log;
    }

    /**
     * @param Account|null $account
     * @return void
     */
    public function setAccount(?Account $account): void
    {
        if ($account && !$account->aws_ses && AwsSes::isEnabled()) {
            throw new DomainException(
                Lang::get('EMAIL_STANDARD_SEND_WITH_SES_ENABLED_ERROR') . ': ' . $account
            );
        }

        $this->account = $account;
    }


    public function addRecipients(array $recipients)
    {
        foreach ($recipients as $recipient) {
            $this->recipients->add($recipient);
        }
    }


    /**
     * @return Mail
     * @throws InvalidArgumentException
     */
    protected function prepareMessage(): Mail
    {
        $mail = new Mail($this->account);
        $mail->setEncoding('utf-8');

        if (count($this->additionalHeaders)) {
            $mail->setAdditionalHeaders(
                $this->additionalHeaders
            );
        }

        if ($this->replyTo) {
            $mail->setReplyTo($this->replyTo);
        }

        if (App::$config->notifications->noreplyRecipient) {
            $this->addNoReplyRecipient($mail);
        }
        return $mail;
    }

    public function hasRecipients(): bool
    {
        if ($_SERVER['ON_DEV']) {
            return true;
        }

        return !empty($this->recipients->get());
    }


    /**
     * @param array $files
     * @param array $varAttachments
     * @return string|null
     * @throws InvalidArgumentException
     */
    public function send(array $files = [], array $varAttachments = []): ?string
    {
        $error = null;
        $currLang = Lang::$lang;
        if (!$this->hasRecipients()) {
            return null;
        }
        $attachments = $this->prepareFiles($files, $varAttachments);
        $recipients = $this->recipients->groupByLangAndLayout();
        $mail = $this->prepareMessage();

        try {
            foreach ($recipients as $layout => $recipientsByLayout) {
                $this->renderer->recipients = $recipientsByLayout;
                if ($this->force || App::$config->notifications->enabled) {
                    foreach ($recipientsByLayout as $key => $langRecipients) {
                        if (App::$config->notifications->disclosedRecipients) {
                            $mail->getHeaders()->removeHeader('to');
                        }
                        $mail->getHeaders()->removeHeader('bcc');

                        $this->setRecipients($mail, $langRecipients);
                        Lang::switchLanguage($key ?: App::$config->settings->defaultLanguage);

                        $this->renderer->setContent($mail, $attachments, $layout);

                        if (
                            (App::$config->notifications->disclosedRecipients && $mail->getTo()->count())
                            || $mail->getBcc()->count()
                        ) {
                            $mail->setSubject($this->baseNotification->getSubject());
                            $this->sendMessage($mail, $attachments);
                        }
                    }
                } else {
                    $this->setRecipients($mail, $recipientsByLayout);
                    $this->renderer->setContent($mail, $attachments, $layout);
                    $mail->setSubject($this->baseNotification->getSubject());
                    $this->sendMessage($mail, $attachments);
                }
            }
        } catch (Exception $e) {
            $error = Lang::get('GENERAL_FAILED_NOTIFICATION') . ' [' . $e->getMessage() . ']. ';

            if (App::$di->has('flash') && $this->useQueue && !$this->muteFlashMessages) {
                App::$di['flash']->error($error);
            }
        }
        Lang::switchLanguage($currLang);

        return $error;
    }


    /**
     * @param Mail $mail
     * @param array $attachments
     * @return $this
     * @throws Exception
     */
    protected function sendMessage(Mail $mail, array $attachments = []): MailNotification
    {
        if (App::$config->notifications->mute) {
            $this->log = $mail->log(-2);
        } else {
            if ($this->useQueue) {
                $this->log = $mail->queue($attachments, $this->queueMode);
            } else {
                $files = $mail->addAttachments($attachments);
                $notificationLogId = $mail->nextLogId();
                $mail->send($notificationLogId);
                if (App::$config->notifications->log) {
                    $this->log = $mail->log(true, $notificationLogId);
                    foreach ($files as $file) {
                        $file->insertRelation($this->log->id());
                    }
                }
            }
        }

        $mail->setTo([]);
        $mail->setBcc([]);
        return $this;
    }

    /**
     * @param Mail $mail
     * @return void
     */
    protected function addNoReplyRecipient(Mail $mail): void
    {
        $noReplyLabel = $this->account->sender_label ?? App::$config->notifications->nameFrom;
        $mail->addTo(App::$config->notifications->noreplyRecipient, $noReplyLabel);
    }


    /**
     * @param array $files
     * @param array $varAttachments
     * @return array
     */
    protected function prepareFiles(array $files = [], array $varAttachments = []): array
    {
        $filesNotAttachment = [];
        $attachments = [];
        $filesSize = 0;
        $maxFilesSize = $this->maxFilesSize * 1024 * 1024;

        if (is_array($files) && $this->attachmentsAsLink) {
            /** @var FilesystemInterface $filesystem */
            $filesystem = App::$di->get('filesystem');

            foreach ($files as $key => $file) {
                $filePath = null;

                if ($file instanceof File) {
                    $filePath = $file->getStoragePath();
                } elseif (isset($file['link'])) {
                    $filePath = $file['link'];
                }

                if ($filePath && $filesystem->has($filePath)) {
                    $filesSize += $filesystem->size($filePath);
                    $ext = pathinfo($file['filename'], PATHINFO_EXTENSION);

                    if (
                        $filesSize > $maxFilesSize
                        || ($file instanceof File && $file->isAudio())
                        || (in_array($ext, ['mp3', 'wav', 'm4a']))
                    ) {
                        $filesNotAttachment[] = $file;
                        unset($files[$key]);
                    }
                }
            }
        }

        // Adding variables to the template
        $this->renderer->filesNotAttachment = $filesNotAttachment;
        $this->renderer->maxFilesSize = $this->maxFilesSize;

        if (App::settings('MailAttachement')) {
            if (is_array($files)) {
                $attachments += $files;
            }
            if (is_array($varAttachments)) {
                $attachments += $varAttachments;
            }
        }

        return $attachments;
    }


    /**
     * @param Mail $mail
     * @param array $recipients
     * @return void
     * @throws NoColumnsException
     */
    protected function setRecipients(Mail $mail, array $recipients)
    {
        if ($this->force || App::$config->notifications->enabled) {
            foreach ($recipients as $recipient) {
                if (App::$config->notifications->disclosedRecipients) {
                    $mail->AddTo($recipient->getEmail());
                } else {
                    $mail->AddBcc($recipient->getEmail());
                }
            }
        } else {
            if (!App::$user->isLogged() || !(App::isSuper() || App::$user->isSwitched())) {
                $mail->addCc(App::$config->settings->testEmail);
                return;
            }

            if (App::ENV_DEVELOPLMENT === APP_ENV) {
                if (!App::$config->settings->testEmail) {
                    throw new Exception(Lang::get('GENERAL_SET_TEST_EMAIL'));
                }
                if (App::$config->settings->testEmail === 'test@velis.pl') {
                    throw new Exception(Lang::get('GENERAL_VELIS_TEST_EMAIL_NOT_ALLOW'));
                }
                $mail->addCc(App::$config->settings->testEmail);
                return;
            }

            if (App::$user->isSwitched()) {
                $mail->addCc(App::$user->getSourceUser()->getEmail());
                return;
            }
            $mail->addCc(App::$user->getEmail());
        }
    }
}
