<?php

namespace Velis\Bpm\Schedule;

use Exception;
use Velis\Acl;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Resource;
use Velis\Bpm\Ticket\Ticket;
use Velis\Dictionary;
use Velis\Exception\BusinessLogicException;
use Velis\Filter;
use Velis\Lang;
use Velis\Model\DataObject;
use Velis\Model\DataObject\NoColumnsException;
use Velis\Model\Sanitizable;
use Velis\Output;
use Velis\User;
use Velis\User\UserProvider;

/**
 * Schedule event model
 * @author Olek Procki <olo@velis.pl>
 *
 * @property int|null $resource_id
 * @property int|null $ticket_id
 */
class Event extends DataObject implements Sanitizable
{
    const DAY_DURATION   = 86400;
    const HOUR_DURATION  = 3600;

    protected Ticket|false|null $_ticket = null;


    /**
     * Free days
     * @var array
     */
    protected static $_freeDays;


    /**
     * @var User[]
     */
    protected static $_priviledgedUsers;


    /**
     * Auto filtering list params
     * @var bool
     */
    protected static $_filterListParams = true;

    /**
     * @return string
     */
    protected function _getTableName()
    {
        return 'app.ticket_schedule_tab';
    }


    /**
     * @return string
     */
    protected function _getListDatasource()
    {
        return 'app.ticket_schedule t';
    }

    /**
     * {@inheritDoc}
     */
    public function add($updateObjectId = true)
    {
        if (isset($this['ticket_schedule_id']) && !$this['ticket_schedule_id']) {
            unset($this['ticket_schedule_id']);
        }

        if (!$this['user_id']) {
            $this['user_id'] = App::$user->id();
        }

        if (App::hasModule('Schedule.CalDav')) {
            if ($user = $this->getUser()) {
                self::$_db->execDML(
                    "UPDATE acl.user_tab SET caldav_ctag = caldav_ctag + 1 WHERE user_id = :user_id",
                    ['user_id' => $user->id()]
                );
            }
        }


        $this->_checkConflict();
        parent::add($updateObjectId);

        if (self::$_db->functionExists('ticket_api.update_duration_by_schedule')) {
            self::$_db->execFunction('ticket_api.update_duration_by_schedule', $this);
        }

        return $this;
    }

    /**
     * Modifies schedule
     *
     * @param bool $calDavRequest
     * @return $this
     * @throws BusinessLogicException
     * @throws Exception
     */
    public function modify($calDavRequest = false)
    {
        if (Filter::validateInt($this->id())) {
            if (isset($this['name'])) {
                $this['name'] = Output::cut($this['name'], 250);
            }

            if (App::hasModule('Schedule.CalDav') && !$calDavRequest) {
                $this['caldav_changed'] = 1;

                if ($user = $this->getUser()) {
                    self::$_db->execDML(
                        'UPDATE acl.user_tab SET caldav_ctag = caldav_ctag + 1 WHERE user_id = :user_id',
                        ['user_id' => $user->id()]
                    );
                }
            }

            $this->_checkConflict();
            parent::modify();

            $params = ['ticket_id' => $this->ticket_id];

            if (self::$_db->functionExists('ticket_api.update_duration_by_schedule')) {
                self::$_db->execFunction('ticket_api.update_duration_by_schedule', $params);
            }
        }

        return $this;
    }


    /**
     * Removes schedule event
     * @return bool
     * @throws Exception
     */
    public function remove()
    {
        $result = false;

        try {
            if (Filter::validateInt($this->id())) {
                self::$_db->startTrans();
                $result = parent::_remove();
            }

            if (App::hasModule('Schedule.CalDav')) {
                if ($user = $this->getUser()) {
                    $user['caldav_ctag'] += 1;
                    $user->save();
                }
            }

            if (Filter::validateInt($this->id())) {
                self::$_db->commit();
            }
        } catch (Exception $e) {
            if (Filter::validateInt($this->id())) {
                self::$_db->rollback();
                $this['deleted']      = 1;
                $this['synchronized'] = 0;

                parent::modify();
            }
            throw new Exception(Lang::get('ACCESS_GOOGLE_REQUEST_ERROR') . ': ' . strip_tags($e->getMessage()), $e->getCode(), $e);
        }

        return $result;
    }


    /**
     * {@inheritDoc}
     */
    public static function getList($page = 1, $params = null, $order = null, $limit = self::ITEMS_PER_PAGE, $fields = null)
    {
        if (!$params['show_deleted'] && !$params['synch_pending']) {
            self::$_listConditions[] = 'deleted=0';
        }

        if ($params['synch_pending']) {
            self::$_listConditions[] = 'synchronized=0';
        }

        if ($params['ref_event_id']) {
            if ($event = self::instance($params['ref_event_id'])) {
                $params['date_from'] = date('Y-m-d', strtotime("last Monday", strtotime($event['date'])));
                $params['date_to']   = date('Y-m-d', strtotime("next Monday", strtotime($event['date'])));
                $params['user_id']   = $event['user_id'];
            }
        }

        if ($params['date_from'] && $params['date_to']) {
            self::$_listConditions[] = "date_to::date >= :date_from::date";
            self::$_listConditions[] = "((:date_from::date, :date_to::date) OVERLAPS (date::date, date_to::date)
                                        OR :date_from::date = date_to::date
                                        OR :date_to::date = date::date)";

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

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

        $dateFrom = $params['date_from'];
        $dateTo   = $params['date_to'];

        unset($params['date_from']);
        unset($params['date_to']);


        if (App::hasModule('Schedule.CalDav')) {
            if ($params['date_caldav_from']) {
                self::$_listConditions[] = "(date + duration * INTERVAL '1 second') > :date_caldav_from";
                self::$_listParams['date_caldav_from'] = $params['date_from'];
            }

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

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


    /**
     * Returns related ticket
     */
    public function getTicket(): Ticket|false|null
    {
        if (!isset($this->_ticket)) {
            if (!($this->_ticket = Ticket::instance($this->ticket_id))) {
                $this->_ticket = false;
            }
        }

        return $this->_ticket;
    }


    /**
     * Return true if the allocation belongs to the logged user
     *
     * @return bool
     */
    public function isMine(): bool
    {
        return $this->user_id == App::$user->id();
    }


    /**
     * Return schedule's user object
     *
     * @return User|null
     * @throws Exception
     */
    public function getUser(): ?User
    {
        $userId = $this['user_id'] ?: $this['person_id'];
        if (!$userId) {
            if ($this->_hasField('person_id')) {
                $field = 'person_id';
            } else {
                $field = 'user_id';
            }
            $userId = $this->$field;
        }

        if (!$userId) {
            return null;
        }

        /** @var UserProvider $userProvider */
        $userProvider = App::getService('userProvider');

        return $userProvider->getUser($userId);
    }


    /**
     * Returns related resource instance
     * @return Resource|null
     */
    public function getResource(): ?Resource
    {
        if ($this->resource_id) {
            return Resource::get($this->resource_id);
        }

        return null;
    }


    /**
     * Return formatted duration
     *
     * @return string
     */
    public function getFormattedDuration()
    {
        if ($this->duration < 3600) {
            return floor($this->duration / 60) . 'min';
        } elseif ($this->duration < 24 * 3600) {
            return floor($this->duration / 3600) . 'h ' . $this->duration % 3600 / 60 . 'min';
        }
    }


    /**
     * Returns schedule edit privileged users
     * @return User[]
     * @throws NoColumnsException
     */
    public static function getPriviledgedUsers()
    {
        if (!isset(self::$_priviledgedUsers)) {
            self::$_priviledgedUsers = Acl::getPrivilegedUsers('Ticket', 'ScheduleEdit');
        }
        return self::$_priviledgedUsers;
    }


    /**
     * Returns list of free days
     * @param bool $omitWeekends
     * @return array
     * @throws \Velis\Exception
     */
    public static function getFreeDays($omitWeekends = false)
    {
        if (!isset(self::$_freeDays)) {
            $weekendStartDay = App::settings('WeekendStartDay', 6, 'Settings');
            self::$_freeDays = App::$cache['free_days'];

            if (null === self::$_freeDays) {
                $query = "SELECT *
                            FROM app.ticket_schedule_day_tab
                           WHERE is_free = 1";
                if ($omitWeekends) {
                    $query .= " AND to_char(day, 'ID') NOT IN ('" . implode("','", [$weekendStartDay , $weekendStartDay  + 1]) . "')";
                }
                self::$_freeDays = Arrays::getColumn(
                    self::$_db->getAll($query),
                    'day'
                );
                App::$cache['free_days'] = self::$_freeDays;
            }
        }

        return self::$_freeDays;
    }


    /**
     * Returns reminder predefined intervals
     * @return array
     */
    public static function getReminderOptions()
    {
        return array (
            "0"       => "0 min",
            "300"     => "5 min",
            "600"     => "10 min",
            "900"     => "15 min",
            "1800"    => "30 min",
            "3600"    => "1 godz.",
            "7200"    => "2 godz.",
            "10800"   => "3 godz.",
            "14400"   => "4 godz.",
            "18000"   => "5 godz.",
            "21600"   => "6 godz.",
            "25200"   => "7 godz.",
            "28800"   => "8 godz.",
            "36000"   => "10 godz.",
            "64800"   => "18 godz.",
            "86400"   => "1 dn.",
            "172800"  => "2 dn.",
            "259200"  => "3 dn.",
            "345600"  => "4 dn.",
            "604800"  => "1 tyg.",
            "1209600" => "2 tyg.",
        );
    }


    /**
     * Returns instance by caldav_uri
     *
     * @param string $objectUri uri of event
     * @return
     */
    public static function byCaldavUri($objectUri)
    {
        $params = array(
            'caldav_uri' => $objectUri
        );

        $events = static::listAll($params);
        return reset($events);
    }


    /**
     * Checks possible dates conflict when dealing with resources
     * @throws BusinessLogicException
     */
    protected function _checkConflict()
    {
        if ($this->resource_id) {
            $query = "SELECT COUNT(*) FROM app.ticket_schedule_tab
                        WHERE resource_id=:resource_id
                          AND ticket_schedule_id != COALESCE(:ticket_schedule_id,0)
                          AND (date + (1||'seconds')::interval - (:bufferTime||' minutes')::interval, (date + (duration - 1||'seconds')::interval + (:bufferTime||' minutes')::interval)) OVERLAPS
                               (:date::timestamp without time zone+(1||'seconds')::interval, :date::timestamp without time zone+(:duration-1||'seconds')::interval)";


            if (App::$config->settings->reservationBufferTime && !$this['all_day']) {
                $bufferTime = App::$config->settings->reservationBufferTime;
            } else {
                $bufferTime = 0;
            }

            $params = [
                'date'               => $this['date'],
                'duration'           => $this['duration'],
                'resource_id'        => $this['resource_id'],
                'ticket_schedule_id' => $this->id() ? $this->id() : null,
                'bufferTime'          => $bufferTime,
            ];

            if (self::$_db->getOne($query, $params)) {
                throw new BusinessLogicException(Lang::get('SCHEDULE_ERROR_RESOURCE_ONE_TIME'));
            }
            return $this;
        }
    }


    /**
     * Checks if date is a free day
     *
     * @param type $day
     * @return string
     */
    public static function isFreeDay($day)
    {

        $params = array(
            'day' => $day,
            'sla_calendar_type_id' => 'HolidayDays'
        );

        $isFreeDay = self::$_db->execFunction(
            'sla_api.is_free_day',
            $params
        );

        if ($isFreeDay) {
            return 'holidays';
        } else {
            $weekendStartDay = App::settings('WeekendStartDay', 6, 'Settings');
            return in_array(date('N', strtotime($day)), [
                $weekendStartDay,
                $weekendStartDay + 1
            ]) ? 'weekends' : 'business_days';
        }
    }


    /**
     * Get possible interval types
     * @return array
     */
    public static function getIntervalTypes()
    {
        return Dictionary::get('app.ticket_schedule_interval');
    }
}
