<?php

namespace Velis\Notification;

use ArrayAccess;
use DateTime;
use Email\Account;
use Exception;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Email\Account as BpmAccount;
use Velis\Lang;
use Velis\Mail\Mail;
use Velis\Model\DataObject;
use Velis\Model\DataObject\NoColumnsException;
use Velis\Model\Sanitizable;
use Velis\Notification;
use Velis\Output;
use Zend\Mail\Headers;

/**
 * Notification log model
 *
 * @author Bartosz Izdebski <bartosz.izdebski@velis.pl>
 * @author Olek Procki <olo@velis.pl>
 * @author Robert Jamróz <robert.jamroz@velis.pl>
 */
class Log extends DataObject implements Sanitizable
{
    use HeadersTrait;


    /**
     * Filter list params by default
     * @var bool
     */
    protected static $_filterListParams = true;


    /**
     * Returns related sql table
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.notification_log_tab';
    }


    /**
     * Returns list datasource name
     * @return string
     */
    protected function _getListDatasource()
    {
        return 'app.notification_log_tab nl';
    }


    /**
     * Constructor
     * @param array|ArrayAccess $data
     * @throws NoColumnsException
     */
    public function __construct($data = null)
    {
        if (isset($data['additional_headers'])) {
            $this->setAdditionalHeaders($data['additional_headers']);
        }

        parent::__construct($data);
    }


    /**
     * {@inheritDoc}
     */
    public function add($updateObjectId = false)
    {
        if (
            $this->hasAdditionalHeaders() &&
            $this->_hasField('additional_headers')
        ) {
            $this['additional_headers'] = Output::jsonEncode(
                $this->getAdditionalHeaders()
            );
        }

        return parent::add($updateObjectId);
    }


    /**
     * {@inheritDoc}
     */
    public function modify($checkDiff = false)
    {
        if (
            $this->hasAdditionalHeaders() &&
            $this->_hasField('additional_headers')
        ) {
            $this['additional_headers'] = Output::jsonEncode(
                $this->getAdditionalHeaders()
            );
        }

        return parent::modify($checkDiff);
    }


    /**
     * Returns task name
     * @return string
     */
    public function getName()
    {
        return $this['subject'];
    }


    /**
     * Returns log method
     *
     * @return string
     */
    public function getNotificationMethod()
    {
        return Method::get($this['notification_method_id']);
    }


    /**
     * Extract attachments recursive
     *
     * @param type $part
     * @param int $attNo
     */
    protected function _extractAttachments($part, &$attNo = null)
    {
        if (!$attNo) {
            $attNo = 1;
        }

        $headers = $part->getHeaders();

        $contentDisposition = null;
        if ($headers->has('content-disposition')) {
            $contentDisposition = $headers->get('content-disposition');
        }

        $contentType = null;
        if ($headers->has('content-type')) {
            $contentType = $headers->get('content-type');
        }

        $filename = null;
        if ($contentType && $contentType->getParameter('name')) {
            $filename = $contentType->getParameter('name');
        } elseif ($contentDisposition && stripos($contentDisposition->getFieldValue(), 'filename') !== false) {
            $value  = str_replace(Headers::FOLDING, " ", $contentDisposition->getFieldValue());
            $values = preg_split('#\s*;\s*#', $value);

            if (count($values)) {
                foreach ($values as $keyValuePair) {
                    list($key, $value) = explode('=', $keyValuePair, 2);
                    $value = trim($value, "'\" \t\n\r\0\x0B");
                    if ($key == 'filename') {
                        $filename = $value;
                    }
                }
            }
        }

        if ($filename || ($contentType && stripos($contentType->getFieldValue(), 'image') !== false)) {
            if ($contentType && !$filename) {
                $filename = Lang::get('EMAIL_ATTACHMENT') . $attNo++;
                $filename .= '.' . strtolower(str_replace('image/', '', $contentType->getFieldValue()));
            }

            $this['attachments'][] = $filename;
        } elseif ($contentType && strpos($contentType->getFieldValue(), 'multipart') !== false) {
            foreach ($part as $p) {
                $this->_extractAttachments($p, $attNo);
            }
        }
    }


    /**
     * Remove outdated logs
     */
    public static function removeOutdated()
    {
        self::$_db->execDML(
            "DELETE FROM app.notification_log_tab WHERE send_date < (CURRENT_TIMESTAMP - INTERVAL '30 days')"
        );
    }


    /**
     * {@inheritDoc}
     */
    public static function getList($page = 1, $params = null, $order = null, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        if ($params['search']) {
            self::$_listConditions[] = '(subject ILIKE :search OR body_txt ILIKE :search OR attachments ILIKE :search)';
            self::$_listParams['search'] = '%' . $params['search'] . '%';
        }

        if ($params['date_start_from']) {
            self::$_listConditions[] = 'send_date >= :date_start_from';
            self::$_listParams['date_start_from'] = $params['date_start_from'] . ' 00:00:00';
        }

        if ($params['date_start_to']) {
            self::$_listConditions[] = 'send_date <= :date_start_to';
            self::$_listParams['date_start_to'] = $params['date_start_to'] . ' 23:59:59';
        }

        if ($params['recipients']) {
            if (!$params['inCc'] && !$params['inBcc']) {
                self::$_listConditions[] = 'recipients ILIKE :recipients';
                self::$_listParams['recipients'] = '%' . $params['recipients'] . '%';
            } elseif ($params['inCc'] && $params['inBcc']) {
                self::$_listConditions[] = 'recipients ILIKE :recipients OR recipients_cc ILIKE :recipients OR recipients_bcc ILIKE :recipients';
                self::$_listParams['recipients'] = '%' . $params['recipients'] . '%';
            } elseif ($params['inCc']) {
                self::$_listConditions[] = 'recipients ILIKE :recipients OR recipients_cc ILIKE :recipients';
                self::$_listParams['recipients'] = '%' . $params['recipients'] . '%';
            } elseif ($params['inBcc']) {
                self::$_listConditions[] = 'recipients ILIKE :recipients OR recipients_bcc ILIKE :recipients';
                self::$_listParams['recipients'] = '%' . $params['recipients'] . '%';
            }

            unset($params['recipients']);
        }

        if ($params['cron_pending']) {
            self::$_listConditions[] = 'is_sent = :sent_flag AND COALESCE(unsuccessful_attempts,0) < :max_attempts';
            self::$_listParams['max_attempts'] = Notification::QUEUE_MAX_SEND_ATTEMPTS;
            self::$_listParams['sent_flag']    = Notification::QUEUE_CRON;
        }

        if ($params['resque_pending']) {
            self::$_listConditions[] = 'n.is_sent= :is_sent';
            self::$_listParams['sent_flag'] = Notification::QUEUE_RESQUE;
        }

        if ($params['exec_time_from']) {
            self::$_listConditions[] = 'exec_time >= :exec_time_from';
            self::$_listParams['exec_time_from'] = $params['exec_time_from'];
        }

        if ($params['exec_time_to']) {
            self::$_listConditions[] = 'exec_time <= :exec_time_to';
            self::$_listParams['exec_time_to'] = $params['exec_time_to'];
        }

        return parent::getList($page, $params, $order, $limit, $fields);
    }


    /**
     * Return cached email sender list
     * @return array
     */
    public static function getSenders()
    {
        if (!isset(App::$cache['notificationLogSenders'])) {
            $query = 'SELECT DISTINCT ON (sender) sender
                        FROM app.notification_log_tab';

            $names = self::$_db->getAll($query);
            $value = array();

            foreach ($names as $name) {
                $value[$name['sender']] = $name['sender'];
            }
            App::$cache['notificationLogSenders'] = $value;
        }
        return App::$cache['notificationLogSenders'];
    }


    /**
     * Return cached email recipient list
     * @return array
     */
    public static function getRecipients()
    {
        if (!isset(App::$cache['notificationLogRecipients'])) {
            $query = 'SELECT DISTINCT ON (recipients) recipients
                        FROM app.notification_log_tab';

            $names = self::$_db->getAll($query);
            $value = array();

            foreach ($names as $name) {
                $value[$name['recipients']] = $name['recipients'];
            }
            App::$cache['notificationLogRecipients'] = $value;
        }
        return App::$cache['notificationLogRecipients'];
    }


    /**
     * Gets email message based on notification log
     * @return Mail
     */
    public function getMail()
    {
        if ($this['email_account_id']) {
            if (class_exists(Account::class)) {
                $account = Account::instance($this['email_account_id']);
            } else {
                $account = BpmAccount::instance($this['email_account_id']);
            }
        } else {
            $account = null;
        }

        $mail = new Mail($account, $this);
        $mail->setEncoding('utf-8');

        if ($this->hasAdditionalHeaders()) {
            $mail->setAdditionalHeaders(
                $this->getAdditionalHeaders()
            );
        }

        $mail->setAdditionalHeader('X-Singu-Notification-Id', sprintf('%s-%s', App::$config->settings->instanceAcro, $this['notification_log_id']));

        $mail->setAttachments(Attachment::listAll(['notification_log_id' => $this->id()]));

        $recipients    = array_filter(explode(',', '' . $this['recipients']));
        $recipientsCc  = array_filter(explode(',', '' . $this['recipients_cc']));
        $recipientsBcc = array_filter(explode(',', '' . $this['recipients_bcc']));

        $replyTo = $this['reply_to'];

        if ($recipients) {
            foreach ($recipients as $item) {
                $mail->addTo(trim($item));
            }
        }

        if ($recipientsCc) {
            foreach ($recipientsCc as $item) {
                $mail->addCc(trim($item));
            }
        }

        if ($recipientsBcc) {
            foreach ($recipientsBcc as $item) {
                $mail->addBcc(trim($item));
            }
        }

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

        $mail->setContents(array(
            'body_text' => $this['body_txt'],
            'body_html' => $this['body_html'],
            'attachments' => $mail->getAttachments()
        ));

        $mail->setSubject($this['subject']);

        return $mail;
    }


    /**
     * Send again email based on notification log
     * @param string|null $email custom email address for email
     * @return Log
     * @throws Exception
     */
    public function sendAgain($email = null)
    {
        if ($email) {
            unset($this['recipients_bcc']);
            unset($this['recipients_cc']);
            $this['recipients'] = $email;
        }

        $mail = $this->getMail();
        $attachments = $mail->getAttachments();
        if (App::$config->resque->enabled == 1 && in_array('notification', Arrays::toArray(App::$config->resque->workers))) {
            $log = $mail->queue($attachments, Notification::QUEUE_RESQUE);
        } else {
            $files = $mail->addAttachments($attachments);
            $mail->send();
            if (App::$config->notifications->log) {
                $log = $mail->log();
                foreach ($files as $file) {
                    $file->insertRelation($log->id());
                }
            }
        }

        return $log;
    }

    /**
     * @return DateTime
     * @throws Exception
     */
    public function getSendDate(): DateTime
    {
        return new DateTime($this->send_date);
    }
}
