<?php

namespace KupShop\KupShopBundle\Util\Mail;

use KupShop\AdminBundle\Util\ActivityLog;
use KupShop\DevelopmentBundle\MailArchive;
use KupShop\KupShopBundle\Util\Compat\ServiceContainer;
use Psr\Log\LoggerInterface;
use Settings;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Address;
use Symfony\Component\Mime\Exception\LogicException;

class EmailSender
{
    private $mailer;
    private $preprocessor;
    private $mailUtil;
    private $logger;

    protected $allowedDomains;

    public function __construct(
        MailerInterface $mailer,
        Preprocessor $preprocessor,
        MailUtil $mailUtil,
        LoggerInterface $logger,
    ) {
        $this->mailer = $mailer;
        $this->preprocessor = $preprocessor;
        $this->mailUtil = $mailUtil;
        $this->logger = $logger;
    }

    public function getSMTP($smtpSettings, $from)
    {
        // Parse From address
        $address = Address::create($from);

        $possibleSettings = [];
        foreach ($smtpSettings as $i => $smtp) {
            foreach (explode(',', $smtp['allowed_senders']) as $email) {
                if ($address->getAddress() === trim($email) && $smtp['enabled'] == 'Y') {
                    $possibleSettings[] = $i;
                }
            }
        }

        if ($possibleSettings) {
            $selectedIndex = $possibleSettings[array_rand($possibleSettings)];
            $smtp = $smtpSettings[$selectedIndex];

            if ($selectedIndex > 0) {
                $smtp['transport'] = "custom{$selectedIndex}";
            }

            return $smtp;
        }

        if (!empty($smtpSettings[0])) {
            return $smtpSettings[0];
        }

        return null;
    }

    /**
     * @param null  $contentType
     * @param null  $attachments
     * @param null  $cc
     * @param null  $bcc
     * @param array $headers
     * @param null  $utm_campaign
     *
     * @return \Symfony\Component\Mime\Email|null
     */
    public function createMessage($From, $For, $Subject, $Message, $contentType = null, $attachments = null, $cc = null, $bcc = null, $headers = [], $utm_campaign = null, $id = 'not yet')
    {
        $cfg = \KupShop\KupShopBundle\Config::get();
        $dbcfg = \Settings::getDefault();
        $smtp_enabled = false;
        $allowedSenders = [];
        if (!empty($From)) {
            $checker = new RFC822();
            try {
                $parts = $checker->parseAddressList($From);
            } catch (\InvalidArgumentException $e) {
                addActivityLog(ActivityLog::SEVERITY_ERROR,
                    ActivityLog::TYPE_COMMUNICATION,
                    "Email má neplatného odesílatele: {$From}",
                    [
                        'error' => $e->getMessage(),
                        'mail_subject' => $Subject,
                        'mail_For' => $For,
                        'mail_utm_campaign' => $utm_campaign,
                    ]);
            }

            $From = $parts[0]->mailbox.'@'.$parts[0]->host;
        }
        // change smtp by settings
        if (!empty($dbcfg->smtp)) {
            $smtp = $this->getSMTP($dbcfg->smtp, $From);

            // check for default smtp
            if (!empty($smtp)) {
                if ($smtp['enabled'] == 'Y' && !empty($smtp['host']) && !empty($smtp['port'])) {
                    $smtp_enabled = true;
                    $allowedSenders = empty($smtp['allowed_senders']) ? [] : array_map(fn ($x) => trim($x), explode(',', $smtp['allowed_senders']));
                    $user = $smtp['user'] ?? null;

                    $users = explode(',', $smtp['user']);
                    // randomly select user if there is more than one
                    if (count($users) > 1) {
                        $user = trim($users[array_rand($users)]);
                    }
                }
            }
        }

        $message = new \Symfony\Component\Mime\Email();
        $messageHeaders = $message->getHeaders();

        // Vybere správný transport pro vybraný SMTP server, symfony to pak zahodí
        $messageHeaders->addTextHeader('X-Transport', $smtp['transport'] ?? 'main');

        $messageHeaders->addTextHeader('X-WPJ-Id', $id);

        if (!empty($cfg['Mail']['DKIM'])) {
            if (is_string($cfg['Mail']['DKIM'])) {
                $message->sender($cfg['Mail']['DKIM']);
            } else {
                $message->sender($dbcfg->shop_email);
            }
        } else {
            $messageHeaders->addTextHeader('X-DKIM-Selector', $cfg['Connection']['database']);
            $messageHeaders->addTextHeader('X-WPJ-Url', $cfg['Addr']['full']);
            if (!empty($dbcfg->bounce_email)) {
                // nastavit sendera jen kdyz je nastaven bounce email
                $message->sender($dbcfg->bounce_email);
                $messageHeaders->addTextHeader('X-WPJ-Sender', $dbcfg->bounce_email);
            }
        }

        foreach ($headers as $header) {
            $h = explode(':', $header);
            if (isset($h[0]) && isset($h[1])) {
                $messageHeaders->addTextHeader(trim($h[0]), trim($h[1]));
            }
        }

        if (!empty($From)) {
            $fromName = str_replace('"', '', $parts[0]->personal);
            $message->replyTo(new Address($From, empty($fromName) ? '' : $fromName));

            $changeFrom = true;
            if ($allowedSenders) {
                if (in_array($From, $allowedSenders) || in_array('*', $allowedSenders)) {
                    $changeFrom = false;
                }
            }

            if ($smtp_enabled && $changeFrom) {
                $firstAllowedSender = reset($allowedSenders);

                if (!empty($user) && str_contains($user, '@')) {
                    $From = $user;
                } elseif ($firstAllowedSender && str_contains($firstAllowedSender, '@')) {
                    $From = $firstAllowedSender;
                } else {
                    $From = $dbcfg->shop_email;
                }
            }

            $message->from(new Address($From, empty($fromName) ? '' : $fromName));
        }

        if (!empty($For) || !empty($bcc)) {
            if (!empty($For)) {
                $checker = new RFC822();
                try {
                    $parts = $checker->parseAddressList($For);
                } catch (\InvalidArgumentException $e) {
                    try {
                        $activityMessage = $message->toString();
                    } catch (LogicException $e) {
                        $activityMessage = $e->getMessage();
                    }
                    addActivityLog(ActivityLog::SEVERITY_ERROR,
                        ActivityLog::TYPE_COMMUNICATION,
                        "Email má neplatného příjemce: {$For}",
                        [
                            'message' => $activityMessage,
                            'error' => $e->getMessage(),
                        ]);
                }

                foreach ($parts as $part) {
                    $message->addTo(new Address($part->mailbox.'@'.$part->host, str_replace('"', '', $part->personal)));
                }
            }
        } else {
            addActivityLog(ActivityLog::SEVERITY_ERROR,
                ActivityLog::TYPE_COMMUNICATION,
                'Email má prázdného příjemce',
                [
                    'message' => $message->toString(),
                ]);
        }

        if (!empty($cc)) {
            $message->addCc($cc);
        }
        if (!empty($bcc)) {
            foreach ($bcc as $bccEmail) {
                $message->addBcc($bccEmail);
            }
        }

        // Make all relative urls absolute
        $Message = $this->preprocessor->absolutizeUrls($Message, $utm_campaign);
        // TODO: je potreba? Pripadne by asi stacilo pouzit $message->embed, ktera to udela za nas
        //        $Message = $this->preprocessor->createEmbeddedMessage($message, $Message);

        $message->subject($Subject);
        $message->html($Message);
        //            ->addPart(translate_shop('switch_to_html', 'email'), 'plain/text');

        if (!empty($attachments)) {
            foreach ($attachments as $attachment) {
                try {
                    if (isset($attachment['text'])) {
                        $message->attach($attachment['text'], $attachment['filename']);
                    } else {
                        $message->attachFromPath($attachment['path'], $attachment['filename']);
                    }
                } catch (\Exception $e) {
                    getRaven()->captureException($e);
                }
            }
        }

        return $message;
    }

    public function sendMessage(\Symfony\Component\Mime\Email $message, bool $failSilently = false): bool
    {
        // TODO: Rozchodit, aby se daly emaily u FE testů odesílat
        if (isDevelopment() || isFrontendTests()) {
            // Umožnit poslat email, pokud se jedná o autodeploy (primárně review branch) a máme nastavený omezení na určitý domény
            if (!(isAutoDeploy() && $this->getAllowedDomains())) {
                /** @var MailArchive $archive */
                $archive = ServiceContainer::getService(MailArchive::class);
                $archive->archive($message);

                return true;
            }
        }

        if (!$this->isAllowedToSendMessage($message)) {
            $this->logger->notice(
                'Message was not allowed to be sent',
                [
                    'subject' => $message->getSubject(),
                    'to' => $this->mailUtil->getStringAddress($message->getTo()),
                    'cc' => $this->mailUtil->getStringAddress($message->getCc()),
                    'bcc' => $this->mailUtil->getStringAddress($message->getBcc()),
                ]
            );

            return false;
        }

        try {
            $res = $this->realSendMessage($message);
        } catch (\Exception $e) {
            $res = false;

            if (!$failSilently) {
                addActivityLog(
                    ActivityLog::SEVERITY_ERROR,
                    ActivityLog::TYPE_COMMUNICATION,
                    'Nepodařilo se odeslat e-mail: '.$message->getSubject().', na e-mail: '.$this->mailUtil->getStringAddress($message->getTo()),
                    [
                        'smtp_error' => $e->getMessage(),
                        'subject' => $message->getSubject(),
                        'from' => $this->mailUtil->getStringAddress($message->getFrom()),
                        'to' => $this->mailUtil->getStringAddress($message->getTo()),
                        'cc' => $this->mailUtil->getStringAddress($message->getCc()),
                        'bcc' => $this->mailUtil->getStringAddress($message->getBcc()),
                        'email_id' => $message->getHeaders()->get('X-WPJ-Id')?->getBody(),
                    ]
                );
            }

            $this->logEmailMessage('Failed to send email', $message);
        }

        return $res;
    }

    public function realSendMessage(\Symfony\Component\Mime\Email $message)
    {
        $this->mailer->send($message);

        $this->logEmailMessage('Sent email', $message);

        return true;
    }

    public function isAllowedToSendMessage(\Symfony\Component\Mime\Email $message): bool
    {
        foreach (['getTo', 'getCc', 'getBcc'] as $method) {
            /** @var Address $address */
            foreach ($message->{$method}() ?? [] as $address) {
                if (!$this->isEmailDomainAllowed($address->getAddress())) {
                    return false;
                }
            }
        }

        return true;
    }

    protected function getAllowedDomains(): array
    {
        if (is_null($this->allowedDomains)) {
            $dbcfg = \Settings::getDefault();

            $allowedDomains = trim($dbcfg->email_allowed_recipient_domains ?? '');
            if (empty($allowedDomains)) {
                return [];
            }

            $this->allowedDomains = array_filter(
                array_map(
                    function ($x) {
                        return trim($x);
                    },
                    explode(',', $allowedDomains)
                )
            );
        }

        return $this->allowedDomains;
    }

    public function isEmailDomainAllowed(string $email): bool
    {
        if (!($allowedDomains = $this->getAllowedDomains())) {
            return true;
        }

        [$_, $domain] = explode('@', $email);

        return in_array(
            $domain,
            $allowedDomains
        );
    }

    private function logEmailMessage(string $logMessage, \Symfony\Component\Mime\Email $message): void
    {
        $this->logger->notice(
            $logMessage,
            [
                'subject' => $message->getSubject(),
                'body' => is_string($message->getHtmlBody()) ? substr(stripTags($message->getHtmlBody()), 0, 200) : 'non-string-body',
                'from' => $this->mailUtil->getStringAddress($message->getFrom()),
                'to' => $this->mailUtil->getStringAddress($message->getTo()),
                'cc' => $this->mailUtil->getStringAddress($message->getCc()),
                'bcc' => $this->mailUtil->getStringAddress($message->getBcc()),
                'email_id' => $message->getHeaders()->get('X-WPJ-Id')?->getBody(),
                'reply_to' => $this->mailUtil->getStringAddress($message->getReplyTo()),
            ]
        );
    }
}
