<?php

namespace Velis;

use LogicException;
use Psr\SimpleCache\InvalidArgumentException;
use RuntimeException;
use Velis\App\Config;
use Velis\Bpm\Email\Account;
use Velis\Mail\Mail;
use Velis\Model\BaseModel;
use Velis\Mvc\View;
use Velis\Mvc\View\Engine\EngineInterface;
use Velis\Notification\ContentModifier\ContentModifierInterface;
use Velis\Notification\HeadersTrait;
use Velis\Notification\Log;
use Velis\Notification\MailNotification;
use Velis\Notification\MailRecipients;
use Velis\Notification\MailRenderer;
use Velis\Notification\Recipient;
use Velis\Notification\SmsNotification;
use Velis\Notification\SmsRecipients;
use Velis\Notification\SmsRenderer;

/**
 * Notification base
 * @author Olek Procki <olo@velis.pl>
 */
abstract class Notification extends BaseModel
{
    use HeadersTrait;

    /** Renderer modes */
    const RENDER_HTML      = 'html';
    const RENDER_HTML_BODY = 'html-body';
    const RENDER_TXT       = 'txt';
    const RENDER_SMS       = 'sms';


    /**
     * Maksymalna próba wysłania
     */
    const QUEUE_MAX_SEND_ATTEMPTS = 5;

    const QUEUE_RESQUE = -1;
    const QUEUE_CRON   =  0;

    const WORKER_QUEUE_NAME = 'notification';


    /**
     * True, jeżeli dana klasa Notification ma wykorzystywać kolejkowanie maili
     *
     * @var bool włączone kolejkowanie maili
     */
    protected static $_queue = false;


    /**
     * Queue mode (cron/resque)
     * @var int
     */
    protected static $_queueMode = self::QUEUE_CRON;


    /**
     * Send large attachments as link
     * @var bool
     */
    protected $_attachmentsAsLink = false;


    /**
     * Notification message renderer
     * @var EngineInterface
     */
    protected $_renderer;


    /**
     * Reply-to email address
     * @var string
     */
    protected $_replyTo;

    /**
     * No reply templates
     * @var array
     */
    protected $_noReplyTemplates = [];

    /**
     * Email account
     * @var Account
     */
    protected $_emailAccount;


    /**
     * Predefined recipients list
     * @var Recipient[]
     */
    protected $_recipients = [];


    /**
     * Sms recipients
     * @var array
     */
    protected $_smsRecipients = [];


    /**
     * @var bool
     */
    protected $_handelSmsErrors = true;


    /**
     * Notification log
     *
     * @var Log
     */
    protected $_logItem = null;


    /**
     * @var Mail
     */
    private $_mail;


    /**
     * Content modifiers
     * @var array;
     */
    private $_contentModifiers = [];


    /**
     * Max file size in MB
     * @var int
     */
    private $_maxFileSize = 5;


    /**
     * @var bool
     */
    public $muteFlashMessages = false;


    /**
     * @var bool
     */
    protected $hideLogo = false;

    /** @var bool  */
    public $showLoginButton = false;

    public bool $force = false;
    public bool $multipleSms = false;

    /**
     * Notification type
     * @var string (one of TYPE_* consts)
     */
    protected $_type;

    /**
     * Constructor
     * @param array $params
     */
    public function __construct(array $params = [])
    {
        /** @var View\Engine\EngineFactory $factory */
        $factory = App::$di->get('templatingEngineFactory');
        $view = new View();
        $this->_renderer = $factory->createEngine($view);
        $config = App::$config->notifications ?: new Config([]);
        $params = new ParameterBag($params);

        if ((int)$config['fileMaxSize'] || (int)$params['fileMaxSize']) {
            $this->setFileMaxSize((int)$params['fileMaxSize'] ?: (int)$config['fileMaxSize']);
        }

        $namespaceTemplatesDir = MODULE_PATH . Arrays::getFirst(explode('\\', get_class($this))) . '/view/notification';
        if (file_exists($namespaceTemplatesDir)) {
            $templateDir = $namespaceTemplatesDir;
        } else {
            $templateDir = APP_PATH . 'view/notification';
        }

        $this->_renderer
            ->setTemplateDir($templateDir)
            ->register(self::class, 'Notification')
        ;

        $this->_LANG = new Lang();

        parent::__construct();
    }


    /**
     * Sets email account
     *
     * @param Account $account
     * @return $this
     */
    public function setAccount($account)
    {
        if ($account) {
            if (!($account instanceof Account)) {
                $this->_emailAccount = Account::get($account);
            } else {
                $this->_emailAccount = $account;
            }
        } else {
            $this->_emailAccount = null;
        }

        return $this;
    }


    /**
     * Adds predefined recipients
     * @param Recipient[] $recipients
     * @return $this
     */
    public function addRecipients($recipients)
    {
        if (!is_array($recipients)) {
            $recipients = array($recipients);
        }
        $this->_recipients = array_merge($this->_recipients, $recipients);

        return $this;
    }


    /**
     * @return Recipient[]
     */
    public function getRecipients(): array
    {
        return $this->_recipients;
    }


    /**
     * Clears recipients
     * @return $this
     */
    public function clearRecipients()
    {
        $this->_recipients = [];

        return $this;
    }



    /**
     * Adds sms recipients
     *
     * @param array $recipients
     * @return $this
     */
    public function addSmsRecipients(array $recipients)
    {
        foreach ($recipients as $recipient) {
            $this->_smsRecipients[] = $recipient;
        }

        return $this;
    }


    /**
     * @param int $size
     * @return $this
     */
    public function setFileMaxSize(int $size): Notification
    {
        $this->_maxFileSize = $size;
        return $this;
    }


    /**
     * Assigns variable to renderer
     *
     * @param string $key
     * @param mixed $value
     */
    public function __set($key, $value)
    {
        $this->_renderer->assign($key, $value);
    }


    /**
     * Returns variable from renderer
     *
     * @param string $key
     * @return mixed
     */
    public function __get($key)
    {
        return $this->_renderer->getVars($key);
    }


    /**
     * Adds new content modifier
     * @param ContentModifierInterface $modifier
     */
    public function addContentModifier(ContentModifierInterface $modifier)
    {
        $this->_contentModifiers[] = $modifier;
    }


    /**
     * Sends notification
     *
     * @param Recipient[]|Recipient $recipients
     * @param array $files
     * @param array $varAttachments
     *
     * @return bool|int
     *
     * @throws LogicException
     * @throws InvalidArgumentException
     */
    public function send($recipients = null, $files = null, $varAttachments = null)
    {
        $mailError = null;
        $smsError = null;
        if (!is_array($recipients)) {
            $recipients = [$recipients];
        }

        $recipients = array_merge($this->_recipients, $recipients);

        $mailNotification = $this->getMailNotification($recipients);

        if ($mailNotification->hasRecipients()) {
            $mailError = $mailNotification->send(
                $files ?? [],
                $varAttachments ?? []
            );
            $this->_logItem = $mailNotification->getLog();
        }

        $smsNotification = $this->getSmsNotification();

        if ($smsNotification->hasRecipients()) {
            $smsError = $smsNotification->sendMessage();
        }

        if (($mailError || $smsError) && !static::isQueue()) {
            throw new RuntimeException($mailError . ', ' . $smsError);
        } elseif (App::$config->notifications->log && $this->_logItem) {
            return $this->_logItem->id();
        } else {
            return true;
        }
    }


    abstract protected function _getMailSubject();


    /**
     * @return string
     */
    public function getSubject()
    {
        return $this->_getMailSubject();
    }


    /**
     * Get notification log
     * @return Log
     */
    public function getLog()
    {
        return $this->_logItem;
    }


    /**
     * Is notification in queue mode
     * @return bool
     */
    public static function isQueue()
    {
        // if notification uses queue
        if (static::$_queue) {
            // when notification queue mode is set to resque
            if (static::$_queueMode == static::QUEUE_RESQUE) {
                // notification queue is not supported by resque worker neither cron fallback is set to true
                if (!static::hasResqueQueue() && !App::$config->notifications->resqueCronFallback) {
                    return false;
                }

                // both resque worker and cron fallback are disabled
                if (!App::$config->resque->enabled && !App::$config->notifications->resqueCronFallback) {
                    return false;
                }
            }

            return true;
        }

        return false;
    }


    /**
     * Enables/disables queue
     * @param bool $enabled
     */
    public static function setQueue($enabled = true)
    {
        static::$_queue = $enabled;
    }

    /**
     * Switch queue mode
     * @param $mode
     * @return void
     */
    public static function setQueueMode($mode)
    {
        if (in_array($mode, [self::QUEUE_RESQUE, self::QUEUE_CRON])) {
            static::$_queueMode = $mode;
        }
    }


    /**
     * Returns true if notification queue is enabled for resque worker
     * @return bool
     */
    public static function hasResqueQueue()
    {
        return in_array(
            self::WORKER_QUEUE_NAME,
            Arrays::toArray(App::$config->resque->workers)
        );
    }


    /**
     * Disable sms errors
     */
    public function muteSmsErrors()
    {
        $this->_handelSmsErrors = false;
    }

    /**
     * @return string
     */
    public function getType(): string
    {
        return $this->_type;
    }

    /**
     * @param $recipients
     * @return MailNotification
     */
    private function getMailNotification($recipients): MailNotification
    {
        $mailRenderer = new MailRenderer(
            $this->_renderer,
            $this->_type,
            $this->_contentModifiers,
            $this->_noReplyTemplates,
            $this->hideLogo,
            $this->showLoginButton
        );

        $mailRecipients = new MailRecipients($recipients);

        return new MailNotification(
            $this,
            $mailRenderer,
            $mailRecipients,
            $this->_emailAccount,
            $this->_additionalHeaders,
            static::isQueue(),
            static::$_queueMode,
            $this->force,
            $this->_attachmentsAsLink,
            $this->_replyTo,
            $this->_maxFileSize
        );
    }

    /**
     * @return SmsNotification
     */
    private function getSmsNotification(): SmsNotification
    {
        $smsRenderer = new SmsRenderer(
            $this->_type,
            $this->_renderer,
            $this->_contentModifiers
        );

        $smsRecipients = new SmsRecipients($this->_smsRecipients);

        return new SmsNotification(
            $smsRenderer,
            $smsRecipients,
            $this->_handelSmsErrors,
            $this->multipleSms
        );
    }
}
