<?php

namespace Velis\Bpm\Ticket;

use ArrayObject;
use Exception;
use Psr\SimpleCache\InvalidArgumentException;
use RuntimeException;
use User\User;
use Velis\App;
use Velis\Exception\BusinessLogicException;
use Velis\Filter;
use Velis\Lang;
use Velis\Model\DataObject\NoColumnsException;
use Velis\Model\File;
use Velis\ParameterBag;
use WideImage\WideImage;

/**
 * Ticket visit model
 * @author Olek Procki <olo@velis.pl>
 */
class Visit extends File
{
    /**
     * Related ticket
     * @var Ticket
     */
    protected $_ticket;


    /**
     * {@inheritDoc}
     */
    protected function _getTableName()
    {
        return 'app.ticket_visit_tab';
    }


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


    /**
     * Returns storage path
     * @return string
     */
    protected function _getStorageDir()
    {
        return App::$config->upload->visitReportDir . DIRECTORY_SEPARATOR;
    }


    /**
     * Returns visit serviceman
     * @return User
     * @throws InvalidArgumentException
     */
    public function getUser()
    {
        return User::get($this['user_id']);
    }


    /**
     * Returns related ticket
     * @return Ticket
     */
    public function getTicket()
    {
        if (!$this->_ticket) {
            $this->_ticket = Ticket::bufferedInstance($this->ticket_id);
        }

        return $this->_ticket;
    }


    /**
     * Sets ticket data
     *
     * @param Ticket $ticket
     * @return $this
     *
     * @throws BusinessLogicException
     */
    public function setTicket(Ticket $ticket)
    {
        if ($this->ticket_id != $ticket->id()) {
            throw new BusinessLogicException(Lang::get('TICKET_VISIT_LINKED_TO_ANOTHER_TICKET'));
        } else {
            $this->_ticket = $ticket;
        }

        return $this;
    }


    /**
     * Returns ticket based on visit attributes (usable when data comes from list data source)
     * @return Ticket
     */
    public function asTicket()
    {
        return Factory::create($this->getArrayCopy());
    }


    /**
     * Uploads report file
     *
     * @param array $file (element of $_FILES)
     * @param bool $unrestricted
     *
     * @return Visit
     *
     * @throws Exception
     */
    public function uploadFile($file, $unrestricted = false)
    {
        if (!is_uploaded_file($file['tmp_name'])) {
            throw new RuntimeException('Wystąpił błąd podczas kopiowania pliku na serwer!');
        }

        if (!($unrestricted || $file['type'] == 'image/jpeg')) {
            if ($file['size'] > App::$config->upload->maxFileSize) {
                throw new RuntimeException('Dopuszczalny rozmiar pliku przekroczony!');
            }
        }

        $filesystem = $this->_getFilesystem();

        if (!$filesystem->upload($this->getStoragePath(), $file['tmp_name'])) {
            throw new RuntimeException('Błąd podczas kopiowania pliku tymczasowego (' . $file['tmp_name'] . ') do: ' . $this->getStoragePath());
        }

        if ($file['type'] == 'image/jpeg') {
            $imageStr = $filesystem->read($this->getStoragePath());
            $img = WideImage::loadFromString($imageStr);

            $newImg = $img->resize(
                App::$config->upload->maxJpgWidth,
                App::$config->upload->maxJpgHeight
            );

            $resizedImageStr = $newImg->asString('jpg', App::$config->upload->jpgQuality);
            $filesystem->write($this->getStoragePath(), $resizedImageStr);
        }

        $this['filename'] = $file['name'];
        $this['type'] = $file['type'];

        return $this->modify();
    }


    /**
     * Returns uploaded file URL
     * @return string
     */
    public function getFileUrl()
    {
        return '/ticket/visit/download-report-file?ticket_visit_id=' . $this->id();
    }


    /**
     * {@inheritDoc}
     * @throws NoColumnsException
     */
    public static function getList($page = 1, $params = null, $order = null, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        $params = new ParameterBag($params);

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


    /**
     * @param array|ArrayObject|null $params
     * @return array
     * @throws \Velis\Exception
     * @throws NoColumnsException
     */
    protected static function _calculateListParams($params = null)
    {
        $openStatuses = self::executeNestedQuery(fn () => Status::getOpenStatuses(true));
        self::$_listConditions = self::$_listConditions ?: [];

        if ($params['search']) {
            if (strpos($params['search'], '#') === 0) {
                self::$_listConditions[] = 'ticket_id = :ticket_id';
                self::$_listParams['ticket_id'] = Filter::filterInt(substr($params['search'], 1));
            } elseif (Filter::validateDigits(trim($params['search']))) {
                self::$_listConditions[] = 'ticket_id = :ticket_id';
                self::$_listParams['ticket_id'] = Filter::filterInt($params['search']);
            } elseif (strpos($params['search'], ',') !== false) {
                $search = implode("', '", explode(',', self::$_db->quote(mb_strtoupper($params['search']))));

                self::$_listConditions[] = '(UPPER(work_description) IN (' . $search . '))';
            } else {
                self::$_listConditions[] = '(work_description ILIKE :search OR title ILIKE :search OR description ILIKE :search)';

                self::$_listParams['search'] = '%' . trim($params['search'], '%') . '%';
            }
        }

        if ($params['ticket_category_id'] && !is_array($params['ticket_category_id'])) {
            $category = new Category($params['ticket_category_id']);
            $categories = [$category->id()];
            if ($category->getChildrenIds()) {
                $categories = array_merge($categories, $category->getChildrenIds());
            }

            $params['ticket_category_id'] = $categories;
        }

        if ($params['visit_from'] && Filter::validateDate($params['visit_from'])) {
            self::$_listConditions[] = "date_trunc('day', date_from) >= :visit_from";
            self::$_listParams['visit_from'] = $params['visit_from'];
        }

        if ($params['visit_to'] && Filter::validateDate($params['visit_to'])) {
            self::$_listConditions[] = "date_trunc('day', date_from) <= :visit_to";
            self::$_listParams['visit_to'] = $params['visit_to'];
        }

        if ($params['end_from'] && Filter::validateDate($params['end_from'])) {
            self::$_listConditions[] = "end_date >= :end_from";
            self::$_listParams['end_from'] = $params['end_from'];
        }

        if ($params['end_to'] && Filter::validateDate($params['end_to'])) {
            self::$_listConditions[] = "end_date <= :end_to";
            self::$_listParams['end_to'] = $params['end_to'];
        }

        if ($params['added_from'] && Filter::validateDate($params['added_from'])) {
            self::$_listConditions[] = "date_trunc('day', date_added) >= :added_from";
            self::$_listParams['added_from'] = $params['added_from'];
        }

        if ($params['added_to'] && Filter::validateDate($params['added_to'])) {
            self::$_listConditions[] = "date_trunc('day', date_added) <= :added_to";
            self::$_listParams['added_to'] = $params['added_to'];
        }

        if ($params['next_step_from'] && Filter::validateDate($params['next_step_from'])) {
            self::$_listConditions[] = "date_trunc('day', next_step_date) >= :next_step_from";
            self::$_listParams['next_step_from'] = $params['next_step_from'];
        }

        if ($params['next_step_to'] && Filter::validateDate($params['next_step_to'])) {
            self::$_listConditions[] = "date_trunc('day', next_step_date) <= :next_step_to";
            self::$_listParams['next_step_to'] = $params['next_step_to'];
        }

        if ($params['visit_hour_from'] && $params['visit_hour_to']) {
            self::$_listConditions[] = "(
                CASE
                    WHEN date_from::TIME > date_to::TIME THEN
                        (date_from::TIME, TIME '24:00:00') OVERLAPS (:hour_from::TIME, :hour_to::TIME) OR
                        (TIME '00:00:00', date_to::TIME) OVERLAPS (:hour_from::TIME, :hour_to::TIME)
                    ELSE
                        CASE
                            WHEN :hour_from::TIME > :hour_to::TIME THEN
                                (date_from::TIME, date_to::TIME) OVERLAPS (:hour_from::TIME, TIME '24:00:00') OR
                                (date_from::TIME, date_to::TIME) OVERLAPS (TIME '00:00:00', :hour_to::TIME)
                            ELSE
                                (date_from::TIME, date_to::TIME) OVERLAPS (:hour_from::TIME, :hour_to::TIME)
                        END
                END
                OR (date_from::TIME = :hour_from::TIME)
                OR (date_to::TIME = :hour_to::TIME)
                OR (date_from::TIME = :hour_to::TIME)
                OR (date_to::TIME = :hour_from::TIME)
            )";
            self::$_listParams['hour_from'] = substr($params['visit_hour_from'], 1, 8);
            self::$_listParams['hour_to'] = substr($params['visit_hour_to'], 1, 8);
        } else {
            if ($params['visit_hour_from']) {
                self::$_listConditions[] = "TO_CHAR(date_from, 'HH24:MI:SS') >= :hour_from";
                self::$_listParams['hour_from'] = substr($params['visit_hour_from'], 1, 8);
            }

            if ($params['visit_hour_to']) {
                self::$_listConditions[] = "TO_CHAR(date_to, 'HH24:MI:SS') <= :hour_to";
                self::$_listParams['hour_to'] = substr($params['visit_hour_to'], 1, 8);
            }
        }

        if ($params['date_from_min']) {
            self::$_listConditions[] = "date_from >= :date_from_min";
            self::$_listParams['date_from_min'] = $params['date_from_min'];
        }

        if ($params['date_to_max']) {
            self::$_listConditions[] = "date_to <= :date_to_max";
            self::$_listParams['date_to_max'] = $params['date_to_max'];
        }

        if (!$params['show_finished'] && !$params['ticket_id']) {
            self::$_listConditions[] = "ticket_status_id IN ('" . implode("', '", $openStatuses) . "')";
        }

        if ($params['user_id']) {
            if (is_array($params['user_id'])) {
                self::$_listConditions[] = "users_ids && ARRAY[" . implode(',', Filter::filterInts($params['user_id'])) . "]::integer[]";
            } else {
                self::$_listConditions[] = ":user = ANY(users_ids)";
                self::$_listParams['user'] = $params['user_id'];
            }
        }

        if ($params['modified_since']) {
            self::$_listConditions[] = "update_date > :modified_since";
            self::$_listParams['modified_since'] = $params['modified_since'];
        }

        if ($params['upcoming_visits']) {
            self::$_listConditions[] = "date_from >= NOW()";
        }

        return array_filter($params->getArrayCopy());
    }
}
