<?php
/**
 * BoxBilling.
 *
 * @copyright BoxBilling, Inc (https://www.boxbilling.org)
 * @license   Apache-2.0
 *
 * Copyright BoxBilling, Inc
 * This source file is subject to the Apache-2.0 License that is bundled
 * with this source code in the file LICENSE
 */

namespace Box\Mod\Support;

class Service implements \Box\InjectionAwareInterface
{
    protected $di;

    public function setDi($di)
    {
        $this->di = $di;
    }

    public function getDi()
    {
        return $this->di;
    }

    public static function onAfterClientOpenTicket(\Box_Event $event)
    {
        $di = $event->getDi();
        $params = $event->getParameters();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');

        try {
            $ticketObj = $supportService->getTicketById($params['id']);
            $identity = $di['loggedin_client'];
            $ticketArr = $supportService->toApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to_client'] = $ticketObj->client_id;
            $email['code'] = 'mod_support_ticket_open';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminOpenTicket(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getTicketById($params['id']);
            $identity = $di['loggedin_admin'];
            $ticketArr = $supportService->toApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to_client'] = $ticketObj->client_id;
            $email['code'] = 'mod_support_ticket_staff_open';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminCloseTicket(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $identity = $di['loggedin_admin'];
            $ticketObj = $supportService->getTicketById($params['id']);
            $ticketArr = $supportService->toApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to_client'] = $ticketObj->client_id;
            $email['code'] = 'mod_support_ticket_staff_close';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminReplyTicket(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getTicketById($params['id']);
            $identity = $di['loggedin_admin'];
            $ticketArr = $supportService->toApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to_client'] = $ticketObj->client_id;
            $email['code'] = 'mod_support_ticket_staff_reply';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterGuestPublicTicketOpen(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getPublicTicketById($params['id']);
            $ticketArr = $supportService->publicToApiArray($ticketObj, true);

            $email = [];
            $email['to'] = $ticketArr['author_email'];
            $email['to_name'] = $ticketArr['author_name'];
            $email['code'] = 'mod_support_pticket_open';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminPublicTicketOpen(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getPublicTicketById($params['id']);
            $identity = $di['loggedin_admin'];
            $ticketArr = $supportService->publicToApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to'] = $ticketArr['author_email'];
            $email['to_name'] = $ticketArr['author_name'];
            $email['code'] = 'mod_support_pticket_staff_open';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminPublicTicketReply(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getPublicTicketById($params['id']);
            $identity = $di['loggedin_admin'];
            $ticketArr = $supportService->publicToApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to'] = $ticketArr['author_email'];
            $email['to_name'] = $ticketArr['author_name'];
            $email['code'] = 'mod_support_pticket_staff_reply';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public static function onAfterAdminPublicTicketClose(\Box_Event $event)
    {
        $di = $event->getDi();
        $supportService = $di['mod_service']('support');
        $emailService = $di['mod_service']('email');
        $params = $event->getParameters();

        try {
            $ticketObj = $supportService->getPublicTicketById($params['id']);
            $identity = $di['loggedin_admin'];
            $ticketArr = $supportService->publicToApiArray($ticketObj, true, $identity);

            $email = [];
            $email['to'] = $ticketArr['author_email'];
            $email['to_name'] = $ticketArr['author_name'];
            $email['code'] = 'mod_support_pticket_staff_close';
            $email['ticket'] = $ticketArr;
            $emailService->sendTemplate($email);
        } catch (\Exception $exc) {
            error_log($exc->getMessage());
        }
    }

    public function getTicketById($id)
    {
        return $this->di['db']->getExistingModelById('SupportTicket', $id, 'Ticket not found');
    }

    public function getPublicTicketById($id)
    {
        return $this->di['db']->getExistingModelById('SupportPTicket', $id, 'Ticket not found');
    }

    /**
     * Return array of ticket statuses.
     */
    public function getStatuses()
    {
        $data = [
            \Model_SupportTicket::OPENED => 'Open',
            \Model_SupportTicket::ONHOLD => 'On hold',
            \Model_SupportTicket::CLOSED => 'Closed',
        ];

        return $data;
    }

    /**
     * Find ticket for client.
     *
     * @param int $id
     *
     * @return \Model_SupportTicket
     */
    public function findOneByClient(\Model_Client $c, $id)
    {
        $bindings = [
            ':id' => $id,
            ':client_id' => $c->id,
        ];

        $ticket = $this->di['db']->findOne('SupportTicket', 'id = :id AND client_id = :client_id', $bindings);

        if (!$ticket instanceof \Model_SupportTicket) {
            throw new \Box_Exception('Ticket not found');
        }

        return $ticket;
    }

    public function getSearchQuery($data)
    {
        $query = 'SELECT st.*
                FROM support_ticket st
                JOIN support_ticket_message stm ON stm.support_ticket_id = st.id
                LEFT JOIN client c ON st.client_id = c.id';

        $search = $this->di['array_get']($data, 'search', null);
        $id = $this->di['array_get']($data, 'id', null);
        $status = $this->di['array_get']($data, 'status', null);
        $client = $this->di['array_get']($data, 'client', null);
        $client_id = $this->di['array_get']($data, 'client_id', null);
        $order_id = $this->di['array_get']($data, 'order_id', null);
        $subject = $this->di['array_get']($data, 'subject', null);
        $content = $this->di['array_get']($data, 'content', null);
        $helpdesk = $this->di['array_get']($data, 'support_helpdesk_id', null);
        $created_at = $this->di['array_get']($data, 'created_at', null);
        $date_from = $this->di['array_get']($data, 'date_from', null);
        $date_to = $this->di['array_get']($data, 'date_to', null);
        $priority = $this->di['array_get']($data, 'priority', null);

        $where = [];
        $bindings = [];

        if ($id) {
            $where[] = 'st.id = :ticket_id';
            $bindings[':ticket_id'] = $id;
        }

        if ($priority) {
            $where[] = 'st.priority = :priority';
            $bindings[':priority'] = $priority;
        }

        if ($helpdesk) {
            $where[] = 'st.support_helpdesk_id = :support_helpdesk_id';
            $bindings[':support_helpdesk_id'] = $helpdesk;
        }

        if ($client) {
            $where[] = 'c.first_name LIKE :first_name';
            $where[] = 'c.last_name LIKE :last_name';
            $bindings[':first_name'] = '%'.$client.'%';
            $bindings[':last_name'] = '%'.$client.'%';
        }

        if ($client_id) {
            $where[] = 'c.id = :client_id';
            $bindings[':client_id'] = $client_id;
        }

        if ($content) {
            $where[] = 'stm.content LIKE :content';
            $bindings[':content'] = '%'.$content.'%';
        }

        if ($subject) {
            $where[] = 'st.subject LIKE :subject';
            $bindings[':subject'] = '%'.$subject.'%';
        }

        if ($status) {
            $where[] = 'st.status = :status';
            $bindings[':status'] = $status;
        }

        if ($order_id) {
            $where[] = 'st.rel_type = :rel_type AND st.rel_id = :rel_id';
            $bindings[':rel_type'] = \Model_SupportTicket::REL_TYPE_ORDER;
            $bindings[':rel_id'] = $order_id;
        }

        if ($created_at) {
            $where[] = "DATE_FORMAT(st.created_at, '%Y-%m-%d') = :created_at";
            $bindings[':created_at'] = date('Y-m-d', strtotime($created_at));
        }

        if ($date_from) {
            $where[] = 'UNIX_TIMESTAMP(st.created_at) >= :date_from';
            $bindings[':date_from'] = strtotime($date_from);
        }

        if ($date_to) {
            $where[] = 'UNIX_TIMESTAMP(st.created_at) <= :date_to';
            $bindings[':date_to'] = strtotime($date_to);
        }
        // smartSearch
        if ($search) {
            if (is_numeric($search)) {
                $where[] = 'st.id = :ticket_id';
                $bindings[':ticket_id'] = $search;
            } else {
                $search = '%'.$search.'%';
                $where[] = '(stm.content LIKE :content OR st.subject LIKE :subject)';
                $bindings[':content'] = $search;
                $bindings[':subject'] = $search;
            }
        }

        if (!empty($where)) {
            $query = $query.' WHERE '.implode(' AND ', $where);
        }

        $query .= ' GROUP BY st.id ORDER BY st.priority ASC, st.id DESC';

        return [$query, $bindings];
    }

    public function counter()
    {
        $query = 'SELECT status, COUNT(id) as counter
                    FROM support_ticket
                    GROUP BY status';

        $data = $this->di['db']->getAssoc($query);

        return [
            'total' => array_sum($data),
            \Model_SupportTicket::OPENED => $this->di['array_get']($data, \Model_SupportTicket::OPENED, 0),
            \Model_SupportTicket::CLOSED => $this->di['array_get']($data, \Model_SupportTicket::CLOSED, 0),
            \Model_SupportTicket::ONHOLD => $this->di['array_get']($data, \Model_SupportTicket::ONHOLD, 0),
        ];
    }

    public function getLatest()
    {
        return $this->di['db']->find('SupportTicket', 'ORDER BY id DESC LIMIT 10');
    }

    public function getExpired()
    {
        $bindings = [
            ':status' => \Model_SupportTicket::ONHOLD,
        ];

        $sql = 'SELECT st.*
                FROM support_ticket as st
                    LEFT JOIN support_helpdesk sh ON sh.id = st.support_helpdesk_id
                WHERE st.status = :status
                AND DATE_ADD(st.updated_at, INTERVAL sh.close_after HOUR) < NOW()
                ORDER BY st.id ASC';

        return $this->di['db']->getAll($sql, $bindings);
    }

    public function countByStatus($status)
    {
        $query = "SELECT COUNT(m.id) as counter FROM support_ticket
                WHERE 'status' = :'status' GROUP BY 'status' LIMIT 1";

        return $this->di['db']->getCell($query, [':status' => $status]);
    }

    public function getActiveTicketsCountForOrder(\Model_ClientOrder $model)
    {
        $query = "SELECT COUNT(id) as counter FROM support_ticket
                WHERE rel_id = :order_id
                AND rel_type = 'order'
                AND (status = :status1 OR status = :status2)";

        $bindings = [
            ':order_id' => $model->id,
            ':status1' => \Model_SupportTicket::OPENED,
            ':status2' => \Model_SupportTicket::ONHOLD,
        ];

        return $this->di['db']->getCell($query, $bindings);
    }

    public function checkIfTaskAlreadyExists(\Model_Client $client, $rel_id, $rel_type, $rel_task)
    {
        $bindings = [
            ':client_id' => $client->id,
            ':rel_id' => $rel_id,
            ':rel_type' => $rel_type,
            ':rel_task' => $rel_task,
            ':rel_status' => \Model_SupportTicket::REL_STATUS_PENDING,
        ];

        $ticket = $this->di['db']->findOne(
            'SupportTicket',
            'client_id = :client_id
            AND rel_id = :rel_id
            AND rel_type = :rel_type
            AND rel_task = :rel_task
            AND rel_status = :rel_status',
            $bindings);

        return $ticket instanceof \Model_SupportTicket;
    }

    public function closeTicket(\Model_SupportTicket $ticket, $identity)
    {
        $ticket->status = \Model_SupportTicket::CLOSED;
        $ticket->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($ticket);

        if ($identity instanceof \Model_Admin) {
            $this->di['events_manager']->fire(['event' => 'onAfterAdminCloseTicket', 'params' => ['id' => $ticket->id]]);
        } elseif ($identity instanceof \Model_Client) {
            $this->di['events_manager']->fire(['event' => 'onAfterClientCloseTicket', 'params' => ['id' => $ticket->id]]);
        }

        $this->di['logger']->info('Closed ticket "%s"', $ticket->id);

        return true;
    }

    public function autoClose(\Model_SupportTicket $model)
    {
        $model->status = \Model_SupportTicket::CLOSED;
        $model->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($model);
        $this->di['logger']->info('Ticket %s was closed', $model->id);

        return true;
    }

    public function canBeReopened(\Model_SupportTicket $model)
    {
        if (\Model_SupportTicket::CLOSED != $model->status) {
            return true;
        }

        $helpdesk = $this->di['db']->getExistingModelById('SupportHelpdesk', $model->support_helpdesk_id);

        return (bool) $helpdesk->can_reopen;
    }

    private function _getRelDetails(\Model_SupportTicket $model)
    {
        if (!$model->rel_type || !$model->rel_id) {
            return [];
        }

        $result = [
            'id' => $model->rel_id,
            'type' => $model->rel_type,
            'task' => $model->rel_task,
            'new_value' => $model->rel_new_value,
            'status' => $model->rel_status,
        ];

        $client = $this->di['db']->load('Client', $model->client_id);

        if (\Model_SupportTicket::REL_TYPE_ORDER == $model->rel_type) {
            $orderService = $this->di['mod_service']('order');
            $o = $orderService->findForClientById($client, $model->rel_id);
            if ($o instanceof \Model_ClientOrder) {
                $result['order'] = $orderService->toApiArray($o, false);
            }
        }

        return $result;
    }

    public function rmByClient(\Model_Client $client)
    {
        $clientTickets = $this->di['db']->find('SupportTicket', 'client_id = :client_id', [':client_id' => $client->id]);
        foreach ($clientTickets as $ticket) {
            $this->di['db']->trash($ticket);
        }
    }

    public function rm(\Model_SupportTicket $model)
    {
        $supportTicketNotes = $this->di['db']->find('SupportTicketNote', 'support_ticket_id = :support_ticket_id', [':support_ticket_id' => $model->id]);
        foreach ($supportTicketNotes as $note) {
            $this->di['db']->trash($note);
        }

        $supportTicketMessages = $this->di['db']->find('SupportTicketMessage', 'support_ticket_id = :support_ticket_id', [':support_ticket_id' => $model->id]);
        foreach ($supportTicketMessages as $message) {
            $this->di['db']->trash($message);
        }

        $id = $model->id;

        $this->di['db']->trash($model);

        $this->di['logger']->info('Removed ticket "%s"', $id);

        return true;
    }

    public function toApiArray(\Model_SupportTicket $model, $deep = true, $identity = null)
    {
        $firstSupportTicketMessage = $this->di['db']->findOne('SupportTicketMessage', 'support_ticket_id = :support_ticket_id ORDER by id ASC LIMIT 1', [':support_ticket_id' => $model->id]);
        $supportHelpdesk = $this->di['db']->load('SupportHelpdesk', $model->support_helpdesk_id);

        $data = $this->di['db']->toArray($model);
        $data['replies'] = $this->messageGetRepliesCount($model);
        $data['first'] = $this->messageToApiArray($firstSupportTicketMessage);
        $data['helpdesk'] = $this->helpdeskToApiArray($supportHelpdesk);
        $data['client'] = $this->getClientApiArrayForTicket($model);

        if ($deep) {
            $messages = $this->messageGetTicketMessages($model);
            foreach ($messages as $msg) {
                $data['messages'][] = $this->messageToApiArray($msg);
            }
        }

        if ($identity instanceof \Model_Admin) {
            $data['rel'] = $this->_getRelDetails($model);
            $data['priority'] = $model->priority;
            $supportTicketNotes = $this->di['db']->find('SupportTicketNote', 'support_ticket_id = :support_ticket_id', [':support_ticket_id' => $model->id]);

            foreach ($supportTicketNotes as $note) {
                $data['notes'][] = $this->noteToApiArray($note);
            }
        }

        return $data;
    }

    public function getClientApiArrayForTicket(\Model_SupportTicket $ticket)
    {
        $client = $this->di['db']->load('Client', $ticket->client_id);

        if ($client instanceof \Model_Client) {
            $clientService = $this->di['mod_service']('client');

            return $clientService->toApiArray($client);
        } else {
            error_log('Missing client for ticket '.$ticket->id);

            return [];
        }
    }

    public function noteGetAuthorDetails(\Model_SupportTicketNote $model)
    {
        $admin = $this->di['db']->load('Admin', $model->admin_id);

        return [
            'name' => $admin->getFullName(),
            'email' => $admin->email,
        ];
    }

    public function noteRm(\Model_SupportTicketNote $model)
    {
        $id = $model->id;
        $this->di['db']->trash($model);

        $this->di['logger']->info('Removed note #%s', $id);

        return true;
    }

    public function noteToApiArray(\Model_SupportTicketNote $model, $deep = false, $identity = null)
    {
        $data = $this->di['db']->toArray($model);
        $data['author'] = $this->noteGetAuthorDetails($model);

        return $data;
    }

    public function helpdeskGetSearchQuery($data)
    {
        $query = 'SELECT * FROM support_helpdesk';

        $search = $this->di['array_get']($data, 'search', null);

        $where = [];
        $bindings = [];

        if ($search) {
            $search = '%'.$search.'%';
            $where[] = '(name LIKE :name OR email LIKE :email OR signature LIKE :signature)';
            $bindings[':name'] = $search;
            $bindings[':email'] = $search;
            $bindings[':signature'] = $search;
        }

        if (!empty($where)) {
            $query = $query.' WHERE '.implode(' AND ', $where);
        }
        $query .= ' ORDER BY id DESC';

        return [$query, $bindings];
    }

    public function helpdeskGetPairs()
    {
        return $this->di['db']->getAssoc('SELECT id, name FROM support_helpdesk');
    }

    public function helpdeskRm(\Model_SupportHelpdesk $model)
    {
        $id = $model->id;

        $tickets = $this->di['db']->find('SupportTicket', 'support_helpdesk_id = :support_helpdesk_id', [':support_helpdesk_id' => $model->id]);
        if (count($tickets) > 0) {
            throw new \Box_Exception('Can not remove helpdesk which has tickets');
        }
        $this->di['db']->trash($model);
        $this->di['logger']->info('Deleted helpdesk #%s', $id);

        return true;
    }

    public function helpdeskToApiArray(\Model_SupportHelpdesk $model)
    {
        return $this->di['db']->toArray($model);
    }

    public function messageGetTicketMessages(\Model_SupportTicket $model)
    {
        return $this->di['db']->find('supportTicketMessage', 'support_ticket_id = :support_ticket_id ORDER BY id ASC', [':support_ticket_id' => $model->id]);
    }

    public function messageGetRepliesCount(\Model_SupportTicket $model)
    {
        $query = 'SELECT COUNT(id) as counter
                    FROM support_ticket_message
                    WHERE support_ticket_id = :support_ticket_id
                    GROUP BY support_ticket_id';

        $bindings = [
            ':support_ticket_id' => $model->id,
        ];

        return $this->di['db']->getCell($query, $bindings);
    }

    public function messageGetAuthorDetails(\Model_SupportTicketMessage $model)
    {
        if ($model->admin_id) {
            $author = $this->di['db']->load('Admin', $model->admin_id);
        } else {
            $author = $this->di['db']->load('Client', $model->client_id);
        }

        return [
            'name' => $author->getFullName(),
            'email' => $author->email,
        ];
    }

    public function messageToApiArray(\Model_SupportTicketMessage $model)
    {
        $data = $this->di['db']->toArray($model);
        $data['author'] = $this->messageGetAuthorDetails($model);

        return $data;
    }

    public function ticketUpdate(\Model_SupportTicket $model, $data)
    {
        $model->support_helpdesk_id = $this->di['array_get']($data, 'support_helpdesk_id', $model->support_helpdesk_id);
        $model->status = $this->di['array_get']($data, 'status', $model->status);
        $model->subject = $this->di['array_get']($data, 'subject', $model->subject);
        $model->priority = $this->di['array_get']($data, 'priority', $model->priority);
        $model->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($model);

        $this->di['logger']->info('Updated ticket #%s', $model->id);

        return true;
    }

    public function ticketMessageUpdate(\Model_SupportTicketMessage $model, $content)
    {
        $model->content = $content;
        $model->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($model);

        return true;
    }

    /**
     * @param \Model_Admin $identity
     */
    public function ticketReply(\Model_SupportTicket $ticket, $identity, $content)
    {
        $msg = $this->di['db']->dispense('SupportTicketMessage');
        $msg->support_ticket_id = $ticket->id;
        if ($identity instanceof \Model_Admin) {
            $msg->admin_id = $identity->id;
        } elseif ($identity instanceof \Model_Client) {
            $msg->client_id = $identity->id;
        }
        $msg->content = $content;
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $msgId = $this->di['db']->store($msg);

        if ($identity instanceof \Model_Admin) {
            $ticket->status = \Model_SupportTicket::ONHOLD;
        } elseif ($identity instanceof \Model_Client) {
            $ticket->status = \Model_SupportTicket::OPENED;
        }

        $ticket->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($ticket);

        if ($identity instanceof \Model_Admin) {
            $this->di['events_manager']->fire(['event' => 'onAfterAdminReplyTicket', 'params' => ['id' => $ticket->id]]);
        } elseif ($identity instanceof \Model_Client) {
            $this->di['events_manager']->fire(['event' => 'onAfterClientReplyTicket', 'params' => ['id' => $ticket->id]]);
        }

        $this->di['logger']->info('Replied to ticket "%s"', $ticket->id);

        return $msgId;
    }

    public function ticketCreateForAdmin(\Model_Client $client, \Model_SupportHelpdesk $helpdesk, $data, \Model_Admin $identity)
    {
        $status = $this->di['array_get']($data, 'status', \Model_SupportTicket::ONHOLD);

        $this->di['events_manager']->fire(['event' => 'onBeforeAdminOpenTicket', 'params' => $data]);

        $ticket = $this->di['db']->dispense('SupportTicket');
        $ticket->client_id = $client->id;
        $ticket->status = $status;
        $ticket->subject = $data['subject'];
        $ticket->support_helpdesk_id = $helpdesk->id;
        $ticket->created_at = date('Y-m-d H:i:s');
        $ticket->updated_at = date('Y-m-d H:i:s');
        $ticketId = $this->di['db']->store($ticket);

        $msg = $this->di['db']->dispense('SupportTicketMessage');
        $msg->admin_id = $identity->id;
        $msg->support_ticket_id = $ticketId;
        $msg->content = $data['content'];
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($msg);

        $this->di['events_manager']->fire(['event' => 'onAfterAdminOpenTicket', 'params' => ['id' => $ticketId]]);

        $this->di['logger']->info('Admin opened new ticket "%s"', $ticketId);

        return (int) $ticketId;
    }

    public function ticketCreateForGuest($data)
    {
        $this->di['validator']->isEmailValid($data['email']);

        $event_params = $data;
        $event_params['ip'] = $this->di['request']->getClientAddress();
        $altered = $this->di['events_manager']->fire(['event' => 'onBeforeGuestPublicTicketOpen', 'params' => $event_params]);

        $status = 'open';
        $subject = $this->di['array_get']($data, 'subject');
        $message = $this->di['array_get']($data, 'message');

        if (is_array($altered)) {
            $status = $this->di['array_get']($altered, 'status');
            $subject = $this->di['array_get']($altered, 'subject');
            $message = $this->di['array_get']($altered, 'message');
        }

        $ticket = $this->di['db']->dispense('SupportPTicket');
        $ticket->hash = sha1(random_bytes(13));
        $ticket->author_name = $data['name'];
        $ticket->author_email = $data['email'];
        $ticket->subject = $subject;
        $ticket->status = $status;
        $ticket->created_at = date('Y-m-d H:i:s');
        $ticket->updated_at = date('Y-m-d H:i:s');
        $ticketId = $this->di['db']->store($ticket);

        $msg = $this->di['db']->dispense('SupportPTicketMessage');
        $msg->support_p_ticket_id = $ticket->id;
        $msg->content = $message;
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($msg);

        $this->di['events_manager']->fire(['event' => 'onAfterGuestPublicTicketOpen', 'params' => ['id' => $ticketId]]);

        $this->di['logger']->info('"%s" opened public ticket "%s"', $ticket->author_email, $ticketId);

        return $ticket->hash;
    }

    public function canClientSubmitNewTicket(\Model_Client $client, array $config)
    {
        $hours = $config['wait_hours'];

        $lastTicket = $this->di['db']->findOne('SupportTicket', 'client_id = :client_id ORDER BY created_at DESC', [':client_id' => $client->id]);
        if (!$lastTicket instanceof \Model_SupportTicket) {
            return true;
        }

        $timeSinceLast = round(abs(strtotime($lastTicket->created_at) - strtotime(date('Y-m-d H:i:s'))) / 3600, 0);

        if ($timeSinceLast < $hours) {
            throw new \Box_Exception(sprintf('You can submit one ticket per %s hours. %s hours left', $hours, $hours - $timeSinceLast));
        }

        return true;
    }

    public function ticketCreateForClient(\Model_Client $client, \Model_SupportHelpdesk $helpdesk, array $data)
    {
        // @todo validate task params
        $rel_id = $this->di['array_get']($data, 'rel_id', null);
        $rel_type = $this->di['array_get']($data, 'rel_type', null);

        if (!is_null($rel_id) && \Model_SupportTicket::REL_TYPE_ORDER == $rel_type) {
            $orderService = $this->di['mod_service']('order');
            $o = $orderService->findForClientById($client, $rel_id);
            if (!$o instanceof \Model_ClientOrder) {
                throw new \Box_Exception('Order ID does not exist');
            }
        }

        $rel_task = $this->di['array_get']($data, 'rel_task', null);
        $rel_new_value = $this->di['array_get']($data, 'rel_new_value', null);
        $rel_status = isset($data['rel_task']) ? \Model_SupportTicket::REL_STATUS_PENDING : \Model_SupportTicket::REL_STATUS_COMPLETE;

        // check if support ticket with same uncompleted task already exists
        if ($rel_id && $rel_type && $rel_task && $this->checkIfTaskAlreadyExists($client, $rel_id, $rel_type, $rel_task)) {
            throw new \Box_Exception('We have already received this request.');
        }

        $mod = $this->di['mod']('support');
        $config = $mod->getConfig();

        if (isset($config['wait_hours']) && is_numeric($config['wait_hours'])) {
            $this->canClientSubmitNewTicket($client, $config);
        }

        $event_params = $data;
        $event_params['client_id'] = $client->id;
        $this->di['events_manager']->fire(['event' => 'onBeforeClientOpenTicket', 'params' => $event_params]);

        $ticket = $this->di['db']->dispense('SupportTicket');
        $ticket->client_id = $client->id;
        $ticket->subject = $data['subject'];
        $ticket->support_helpdesk_id = $helpdesk->id;
        $ticket->created_at = date('Y-m-d H:i:s');
        $ticket->updated_at = date('Y-m-d H:i:s');

        // related task with ticket
        $ticket->rel_id = $rel_id;
        $ticket->rel_type = $rel_type;
        $ticket->rel_task = $rel_task;
        $ticket->rel_new_value = $rel_new_value;
        $ticket->rel_status = $rel_status;

        $ticketId = $this->di['db']->store($ticket);

        $this->messageCreateForTicket($ticket, $client, $data['content']);

        $this->di['events_manager']->fire(['event' => 'onAfterClientOpenTicket', 'params' => ['id' => $ticket->id]]);

        if (isset($config['autorespond_enable'])
            && $config['autorespond_enable']
            && isset($config['autorespond_message_id'])
            && !empty($config['autorespond_message_id'])
        ) {
            $this->cannedReply($ticket, $config['autorespond_message_id']);
        }

        $this->di['logger']->info('Submitted new ticket "%s"', $ticketId);

        return (int) $ticketId;
    }

    private function cannedReply(\Model_SupportTicket $ticket, $cannedId)
    {
        try {
            $cannedObj = $this->di['db']->getExistingModelById('SupportPr', $cannedId, 'Canned reply not found');
            $canned = $this->cannedToApiArray($cannedObj);
            $staffService = $this->di['mod_service']('staff');
            $admin = $staffService->getCronAdmin();
            if (isset($canned['content']) && $admin instanceof \Model_Admin) {
                $this->ticketReply($ticket, $admin, $canned['content']);
            }
        } catch (\Exception $e) {
            error_log($e->getMessage());
        }
    }

    /**
     * @param \Model_Client $identity
     */
    public function messageCreateForTicket(\Model_SupportTicket $ticket, $identity, $content)
    {
        $msg = $this->di['db']->dispense('SupportTicketMessage');
        $msg->support_ticket_id = $ticket->id;
        if ($identity instanceof \Model_Admin) {
            $msg->admin_id = $identity->id;
        } elseif ($identity instanceof \Model_Client) {
            $msg->client_id = $identity->id;
        } else {
            throw new \Box_Exception('Identity is not valid');
        }
        $msg->content = $content;
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');

        return $this->di['db']->store($msg);
    }

    public function publicGetStatuses()
    {
        $data = [
            \Model_SupportPTicket::OPENED => 'Open',
            \Model_SupportPTicket::ONHOLD => 'On hold',
            \Model_SupportPTicket::CLOSED => 'Closed',
        ];

        return $data;
    }

    public function publicFindOneByHash($hash)
    {
        $bindings = [
            ':hash' => $hash,
        ];

        $publicTicket = $this->di['db']->findOne('SupportPTicket', 'hash = :hash', $bindings);
        if (!$publicTicket instanceof \Model_SupportPTicket) {
            throw new \Box_Exception('Public ticket not found');
        }

        return $publicTicket;
    }

    public function publicGetSearchQuery($data)
    {
        $query = 'SELECT spt.* FROM support_p_ticket spt
        LEFT JOIN support_p_ticket_message sptm
        ON spt.id = sptm.support_p_ticket_id';

        $search = $this->di['array_get']($data, 'search', null);

        $id = $this->di['array_get']($data, 'id', null);
        $status = $this->di['array_get']($data, 'status', null);
        $name = $this->di['array_get']($data, 'name', null);
        $email = $this->di['array_get']($data, 'email', null);
        $subject = $this->di['array_get']($data, 'subject', null);
        $content = $this->di['array_get']($data, 'content', null);

        $where = [];
        $bindings = [];

        if ($id) {
            $where[] = 'spt.id  = :p_ticket_id';
            $bindings[':p_ticket_id'] = $id;
        }

        if ($status) {
            $where[] = 'spt.status  = :p_ticket_status';
            $bindings[':p_ticket_status'] = $status;
        }

        if ($email) {
            $where[] = 'spt.author_email  = :p_ticket_author_email';
            $bindings[':p_ticket_author_email'] = $email;
        }

        if ($name) {
            $where[] = 'spt.author_name  = :p_ticket_author_name';
            $bindings[':p_ticket_author_name'] = $name;
        }

        if ($content) {
            $where[] = 'spt.content LIKE :p_ticket_content';
            $bindings[':p_ticket_content'] = "%$content%";
        }

        if ($subject) {
            $where[] = 'spt.subject LIKE :p_ticket_subject';
            $bindings[':p_ticket_subject'] = "%$subject%";
        }

        // smartSearch
        if ($search) {
            if (is_numeric($search)) {
                $where[] = 'spt.id = :p_ticket_id';
                $bindings[':p_ticket_id'] = $search;
            } else {
                $search = '%'.$search.'%';
                $where[] = 'sptm.content LIKE :p_message_content OR spt.subject LIKE :p_ticket_subject';
                $bindings[':p_message_content'] = $search;
                $bindings[':p_ticket_subject'] = $search;
            }
        }

        if (!empty($where)) {
            $query = $query.' WHERE '.implode(' AND ', $where);
        }

        $query .= ' GROUP BY spt.id, sptm.id ORDER BY spt.id DESC, sptm.id ASC';

        return [$query, $bindings];
    }

    public function publicCounter()
    {
        $query = 'SELECT status, COUNT(id) as counter
                FROM support_p_ticket
                GROUP BY status';

        $data = $this->di['db']->getAssoc($query);

        return [
            'total' => array_sum($data),
            \Model_SupportPTicket::OPENED => $data[\Model_SupportPTicket::OPENED] ?? 0,
            \Model_SupportPTicket::CLOSED => $data[\Model_SupportPTicket::CLOSED] ?? 0,
            \Model_SupportPTicket::ONHOLD => $data[\Model_SupportPTicket::ONHOLD] ?? 0,
        ];
    }

    public function publicGetLatest()
    {
        return $this->di['db']->find('SupportPTicket', 'ORDER BY id DESC Limit 10');
    }

    public function publicCountByStatus($status)
    {
        $query = 'SELECT COUNT(id) as counter
                FROM support_p_ticket
                WHERE status = :status
                GROUP BY status';

        return $this->di['db']->getCell($query, [':status' => $status]);
    }

    public function publicGetExpired()
    {
        $bindings = [
            ':status' => \Model_SupportPTicket::ONHOLD,
        ];

        $publicTicket = $this->di['db']->find('SupportPTicket', 'status = :status AND DATE_ADD(updated_at, INTERVAL 48 HOUR) < NOW() ORDER BY id ASC', $bindings);

        return $publicTicket;
    }

    public function publicCloseTicket(\Model_SupportPTicket $model, $identity)
    {
        $model->status = 'closed';
        $model->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($model);

        if ($identity instanceof \Model_Admin) {
            $this->di['events_manager']->fire(['event' => 'onAfterAdminPublicTicketClose', 'params' => ['id' => $model->id]]);
            $this->di['logger']->info('Public Ticket %s was closed', $model->id);
        } elseif ($identity instanceof \Model_Guest) {
            $this->di['events_manager']->fire(['event' => 'onAfterGuestPublicTicketClose', 'params' => ['id' => $model->id]]);
            $this->di['logger']->info('"%s" closed public ticket "%s"', $model->author_email, $model->id);
        }

        return true;
    }

    public function publicAutoClose(\Model_SupportPTicket $model)
    {
        $model->status = 'closed';
        $model->updated_at = date('Y-m-d H:i:s');

        $this->di['db']->store($model);

        $this->di['logger']->info('Public Ticket %s was closed', $model->id);

        return true;
    }

    public function publicRm(\Model_SupportPTicket $model)
    {
        $id = $model->id;
        $bindings = [
            ':support_p_ticket_id' => $model->id,
        ];
        $messages = $this->di['db']->find('SupportPTicketMessage', 'support_p_ticket_id = :support_p_ticket_id', $bindings);

        foreach ($messages as $message) {
            $this->di['db']->trash($message);
        }

        $this->di['db']->trash($model);

        $this->di['logger']->info('Deleted public ticket #%s', $id);

        return true;
    }

    public function publicToApiArray(\Model_SupportPTicket $model, $deep = true)
    {
        $data = $this->di['db']->toArray($model);
        $messages = [];
        $messagesArr = $this->di['db']->find('SupportPTicketMessage', 'support_p_ticket_id = :support_p_ticket_id ORDER BY id', [':support_p_ticket_id' => $model->id]);
        foreach ($messagesArr as $msg) {
            $messages[] = $this->publicMessageToApiArray($msg);
        }

        $first = reset($messagesArr);
        if ($first instanceof \Model_SupportPTicketMessage) {
            $data['author'] = $this->publicMessageGetAuthorDetails($first);
        } else {
            $data['author'] = [];
        }
        $data['messages'] = $messages;

        return $data;
    }

    public function publicMessageGetAuthorDetails(\Model_SupportPTicketMessage $model)
    {
        if ($model->admin_id) {
            $author = $this->di['db']->getExistingModelById('Admin', $model->admin_id);

            return [
                'name' => $author->getFullName(),
                'email' => $author->email,
            ];
        }

        $ticket = $this->di['db']->getExistingModelById('SupportPTicket', $model->support_p_ticket_id);

        return [
            'name' => $ticket->author_name,
            'email' => $ticket->author_email,
        ];
    }

    public function publicMessageToApiArray(\Model_SupportPTicketMessage $model, $deep = true)
    {
        $data = $this->di['db']->toArray($model);
        $data['author'] = $this->publicMessageGetAuthorDetails($model);

        return $data;
    }

    public function publicTicketCreate($data, \Model_Admin $identity)
    {
        $this->di['validator']->isEmailValid($data['email']);

        $this->di['events_manager']->fire(['event' => 'onBeforeAdminPublicTicketOpen', 'params' => $data]);

        $ticket = $this->di['db']->dispense('SupportPTicket');
        $ticket->hash = sha1(random_bytes(13));
        $ticket->author_name = $data['name'];
        $ticket->author_email = $data['email'];
        $ticket->subject = $data['subject'];
        $ticket->status = 'open';
        $ticket->created_at = date('Y-m-d H:i:s');
        $ticket->updated_at = date('Y-m-d H:i:s');
        $ticketId = $this->di['db']->store($ticket);

        $msg = $this->di['db']->dispense('SupportPTicketMessage');
        $msg->support_p_ticket_id = $ticketId;
        $msg->admin_id = $identity->id;
        $msg->content = $data['message'];
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($msg);

        $this->di['events_manager']->fire(['event' => 'onAfterAdminPublicTicketOpen', 'params' => ['id' => $ticketId]]);

        $this->di['logger']->info('Opened public ticket for email "%s"', $ticket->author_email);

        return $ticketId;
    }

    public function publicTicketUpdate(\Model_SupportPTicket $model, $data)
    {
        $model->subject = $this->di['array_get']($data, 'subject', $model->subject);
        $model->status = $this->di['array_get']($data, 'status', $model->status);
        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        $this->di['logger']->info('Updated public ticket #%s', $model->id);

        return true;
    }

    public function publicTicketReply(\Model_SupportPTicket $ticket, \Model_Admin $identity, $content)
    {
        $msg = $this->di['db']->dispense('SupportPTicketMessage');
        $msg->support_p_ticket_id = $ticket->id;
        $msg->admin_id = $identity->id;
        $msg->content = $content;
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $messageId = $this->di['db']->store($msg);

        $ticket->status = \Model_SupportPTicket::ONHOLD;
        $ticket->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($ticket);

        $this->di['events_manager']->fire(['event' => 'onAfterAdminPublicTicketReply', 'params' => ['id' => $ticket->id]]);

        $this->di['logger']->info('Replied to public ticket "%s"', $ticket->id);

        return $messageId;
    }

    public function publicTicketReplyForGuest(\Model_SupportPTicket $ticket, $message)
    {
        $msg = $this->di['db']->dispense('SupportPTicketMessage');
        $msg->support_p_ticket_id = $ticket->id;
        $msg->content = $message;
        $msg->ip = $this->di['request']->getClientAddress();
        $msg->created_at = date('Y-m-d H:i:s');
        $msg->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($msg);

        $ticket->status = \Model_SupportPTicket::OPENED;
        $ticket->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($ticket);

        $this->di['events_manager']->fire(['event' => 'onAfterGuestPublicTicketReply', 'params' => ['id' => $ticket->id]]);

        $this->di['logger']->info('Client "%s" replied to public ticket "%s"', $ticket->author_email, $ticket->id);

        return $ticket->hash;
    }

    public function helpdeskUpdate(\Model_SupportHelpdesk $model, $data)
    {
        $model->name = $this->di['array_get']($data, 'name', $model->name);
        $model->email = $this->di['array_get']($data, 'email', $model->email);
        $model->can_reopen = $this->di['array_get']($data, 'can_reopen', $model->can_reopen);
        $model->close_after = $this->di['array_get']($data, 'close_after', $model->close_after);
        $model->signature = $this->di['array_get']($data, 'signature', $model->signature);
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Updated helpdesk #%s', $id);

        return true;
    }

    public function helpdeskCreate($data)
    {
        $model = $this->di['db']->dispense('SupportHelpdesk');
        $model->name = $data['name'];
        $model->email = $this->di['array_get']($data, 'email', null);
        $model->can_reopen = $this->di['array_get']($data, 'can_reopen', null);
        $model->close_after = $this->di['array_get']($data, 'close_after', null);
        $model->signature = $this->di['array_get']($data, 'signature', null);
        $model->created_at = date('Y-m-d H:i:s');
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Created helpdesk #%s', $id);

        return $id;
    }

    public function cannedGetSearchQuery($data)
    {
        $query = 'SELECT sp.* FROM support_pr sp
                LEFT JOIN support_pr_category spc
                ON spc.id = sp.support_pr_category_id';

        $search = $this->di['array_get']($data, 'search', null);

        $where = [];
        $bindings = [];

        if ($search) {
            $search = '%'.$search.'%';
            $where[] = 'title LIKE :title OR content LIKE :content';
            $bindings[':title'] = $search;
            $bindings[':content'] = $search;
        }

        if (!empty($where)) {
            $query = $query.' WHERE '.implode(' AND ', $where);
        }

        $query .= ' ORDER BY sp.support_pr_category_id ASC';

        return [$query, $bindings];
    }

    public function cannedGetGroupedPairs()
    {
        $query = 'SELECT sp.title as r_title, spc.title as c_title FROM support_pr sp
                LEFT JOIN support_pr_category spc
                ON spc.id = sp.support_pr_category_id';

        $data = $this->di['db']->getAll($query);
        $res = [];
        foreach ($data as $r) {
            $res[$r['c_title']][$r['id']] = $r['r_title'];
        }

        return $res;
    }

    public function cannedRm(\Model_SupportPr $model)
    {
        $id = $model->id;

        $this->di['db']->trash($model);

        $this->di['logger']->info('Deleted canned reponse #%s', $id);

        return true;
    }

    public function cannedToApiArray(\Model_SupportPr $model)
    {
        $result = $this->di['db']->toArray($model);
        $category = $this->di['db']->load('SupportPrCategory', $model->support_pr_category_id);
        if ($category instanceof \Model_SupportPrCategory) {
            $result['category'] = [
                'id' => $category->id,
                'title' => $category->title,
            ];
        } else {
            $result['category'] = [];
        }

        return $result;
    }

    public function cannedCategoryGetPairs()
    {
        return $this->di['db']->getAssoc('SELECT id, title FROM support_pr_category');
    }

    public function cannedCategoryRm(\Model_SupportPrCategory $model)
    {
        $id = $model->id;
        $this->di['db']->trash($model);
        $this->di['logger']->info('Deleted canned response category #%s', $id);

        return true;
    }

    public function cannedCategoryToApiArray(\Model_SupportPrCategory $model)
    {
        return $this->di['db']->toArray($model);
    }

    public function cannedCreate($title, $categoryId, $content = null)
    {
        $systemService = $this->di['mod_service']('system');
        $systemService->checkLimits('Model_SupportPr', 5);

        $model = $this->di['db']->dispense('SupportPr');
        $model->support_pr_category_id = $categoryId;
        $model->title = $title;
        $model->content = $content;
        $model->created_at = date('Y-m-d H:i:s');
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Created new canned response #%s', $id);

        return $id;
    }

    public function cannedUpdate(\Model_SupportPr $model, $data)
    {
        $model->support_pr_category_id = $this->di['array_get']($data, 'category_id', $model->support_pr_category_id);
        $model->title = $this->di['array_get']($data, 'title', $model->title);
        $model->content = $this->di['array_get']($data, 'content', $model->content);
        $model->updated_at = date('Y-m-d H:i:s');
        $this->di['db']->store($model);

        $this->di['logger']->info('Updated canned response #%s', $model->id);

        return true;
    }

    public function cannedCategoryCreate($title)
    {
        $model = $this->di['db']->dispense('SupportPrCategory');
        $model->title = $title;
        $model->created_at = date('Y-m-d H:i:s');
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Created new canned response category #%s', $id);

        return $id;
    }

    public function cannedCategoryUpdate(\Model_SupportPrCategory $model, $title)
    {
        $model->title = $title;
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Updated canned response category #%s', $id);

        return true;
    }

    public function noteCreate(\Model_SupportTicket $ticket, \Model_Admin $identity, $note)
    {
        $model = $this->di['db']->dispense('SupportTicketNote');
        $model->support_ticket_id = $ticket->id;
        $model->admin_id = $identity->id;
        $model->note = $note;
        $model->created_at = date('Y-m-d H:i:s');
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Added note to ticket #%s', $id);

        return $id;
    }

    public function ticketTaskComplete(\Model_SupportTicket $model)
    {
        $model->rel_status = \Model_SupportTicket::REL_STATUS_COMPLETE;
        $model->updated_at = date('Y-m-d H:i:s');
        $id = $this->di['db']->store($model);

        $this->di['logger']->info('Marked ticket #%s task as complete', $id);

        return true;
    }
}
