<?php

namespace Velis\Bpm\Ticket\Mixin;

use DomainException;
use InvalidArgumentException;
use ReflectionException;
use Velis\App;
use Velis\Arrays;
use Velis\Bpm\Department;
use Velis\Bpm\Person;
use Velis\Lang;
use Velis\Model\DataObject;
use Velis\Model\DataObject\NoColumnsException;
use Velis\PersonInterface;
use Velis\User;
use Velis\User\UserProvider;

/**
 * Ticket persons functionality
 * @author Olek Procki <olo@velis.pl>
 */
trait PersonTrait
{
    /**
     * Involved users
     * @var User[]
     */
    protected $_users;


    /**
     * Persons & users related with ticket
     * @var PersonInterface[]
     */
    protected $_persons;


    /**
     * Owner person
     * @var PersonInterface
     */
    protected $_ownerPerson;


    /**
     * Ticket owner user
     * @var User
     */
    protected $_ownerUser;


    /**
     * Ticket operator
     * @var User
     */
    protected $_responsiblePerson;


    /**
     * Ticket operators
     * @var User[]
     */
    protected $_operators;


    /**
     * Returns ticket reporter (author)
     * @return User|PersonInterface
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getReporter()
    {
        if ($this->_persons && array_key_exists($this->added_by_user_id, $this->_persons)) {
            return $this->_persons[$this->added_by_user_id];
        } elseif ($this->_users && array_key_exists($this->added_by_user_id, $this->_users)) {
            return $this->_users[$this->added_by_user_id];
        } else {
            /** @var UserProvider $userProvider */
            $userProvider = App::getService('userProvider');

            return $userProvider->getUser($this->added_by_user_id);
        }
    }


    /**
     * Returns ticket owner User or Person
     * @return User|PersonInterface|bool
     * @throws ReflectionException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getOwner()
    {
        if ($this->_persons && array_key_exists($this->owner_user_id, $this->_persons)) {
            return $this->_persons[$this->owner_user_id];
        } elseif ($this->_users && array_key_exists($this->owner_user_id, $this->_users)) {
            return $this->_users[$this->owner_user_id];
        } elseif ($this->getOwnerUser()) {
            return $this->getOwnerUser();
        } else {
            return $this->getOwnerPerson();
        }
    }


    /**
     * Returns ticket owner
     * @return User|null
     */
    public function getOwnerUser(): ?User
    {
        if (!$this->_ownerUser) {
            if ($this->_users && array_key_exists($this->owner_user_id, $this->_users)) {
                return $this->_users[$this->owner_user_id];
            } elseif ($this->owner_user_id) {
                /** @var UserProvider $userProvider */
                $userProvider = App::getService('userProvider');
                $this->_ownerUser = $userProvider->getUser($this->owner_user_id);
            }
        }

        return $this->_ownerUser;
    }

    public function getOwnerUserId(): ?int
    {
        return $this->owner_user_id;
    }


    /**
     * Returns owner person
     * @return Person|void
     * @throws ReflectionException
     */
    public function getOwnerPerson()
    {
        if (!isset($this->_ownerPerson)) {
            if ($this->_hasField('owner_person_id')) {
                $colName = 'owner_person_id';
            } else {
                $colName = 'person_id';
            }

            if ($this->{$colName}) {
                // desperately need getListDatasource attributes, so simple ::instance not possible
                $persons = Person::listAll(array('person_id' => $this->{$colName}));
                $this->_ownerPerson = current($persons);
            } else {
                $this->_ownerPerson = false;
            }
        }

        if ($this->_ownerPerson) {
            return $this->_ownerPerson;
        }
    }


    /**
     * Sets ticket owner
     *
     * @param mixed $owner
     * @return $this
     * @throws InvalidArgumentException
     * @throws DomainException
     */
    public function setOwner($owner)
    {
        /** @var UserProvider $userProvider */
        $userProvider = App::getService('userProvider');

        if ($this->_hasField('owner_person_id')) {
            $this['owner_person_id'] = $owner instanceof DataObject ? $owner->id() : $owner;
        } elseif ($owner instanceof User) {
            $this['owner_user_id'] = $owner->id();
            $this['person_id'] = null;
        } elseif ($owner instanceof Person) {
            $this['person_id'] = $owner->id();
            $this['owner_user_id'] = null;
        } elseif (is_int($owner) && $userProvider->getUser($owner)) {
            $this['owner_user_id'] = $owner;
            $this['person_id'] = null;
        }

        return $this;
    }


    /**
     * Returns ticket operator
     * @return User|null
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getOperator(): ?User
    {
        if (!$this->responsible_user_id) {
            return null;
        }

        if ($this->_persons && array_key_exists($this->responsible_user_id, $this->_persons)) {
            $this->_responsiblePerson = $this->_persons[$this->responsible_user_id];
        } elseif ($this->_users && array_key_exists($this->responsible_user_id, $this->_users)) {
            $this->_responsiblePerson = $this->_users[$this->responsible_user_id];
        } else {
            /** @var UserProvider $userProvider */
            $userProvider = App::getService('userProvider');
            $this->_responsiblePerson = $userProvider->getUser($this->responsible_user_id);
        }

        return $this->_responsiblePerson ?? null;
    }


    /**
     * Returns ticket operators
     * @return User[]
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getOperators()
    {
        if ($category = $this->getCategory()) {
            if (!isset($this->_operators)) {
                $this->_operators = $category->getOperators();
            }
            return $this->_operators;
        } else {
            return [];
        }
    }


    /**
     * Returns responsible department
     * @return Department
     */
    public function getResponsibleDepartment()
    {
        if ($this->_hasField('responsible_department_id') && $this->responsible_department_id) {
            if (class_exists('User\User\Department')) {
                return \User\User\Department::get($this->responsible_department_id);
            } else {
                return Department::get($this->responsible_department_id);
            }
        }
    }


    /**
     * Returns responsible person or department
     * @return Department|User
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getResponsible()
    {
        $responsible = $this->getOperator();

        if (!$responsible) {
            $responsible = $this->getResponsibleDepartment();
        }

        return $responsible;
    }


    /**
     * Returns query for ticket's person list
     * Can be overrided to follow ticket spec data definiton
     *
     * @return string
     */
    protected function _getPersonListQuery()
    {
        return "SELECT
                    p.*,
                    COALESCE(active_account,1) AS active
                  FROM app.person p
                  WHERE (
                        (p.is_user=1 AND p.company_id IS NULL) OR
                         p.company_id IN (
                            SELECT company_id FROM app.ticket_tab t WHERE t.ticket_id = :ticket_id
                        )
                        OR p.person_id IN (
                            SELECT
                              person_id
                            FROM app.ticket_log_tab tl
                            WHERE tl.ticket_id = :ticket_id
                        )
                ) ORDER BY
                  p.company_id IS NOT NULL,
                  company_short_name NULLS FIRST,
                  company_name NULLS FIRST,
                  last_name,
                  first_name";
    }


    /**
     * Loads ticket persons
     * @return $this
     * @throws NoColumnsException
     */
    public function loadPersons()
    {
        $this->_persons = [];
        $sql = $this->_getPersonListQuery();

        foreach (self::$_db->getAll($sql, $this->_getPrimaryKeyParam()) as $row) {
            if ($row['is_user']) {
                /** @var UserProvider $userProvider */
                $userProvider = App::getService('userProvider');
                $this->_persons[$row['person_id']] = $userProvider->createUser($row);
            } else {
                $this->_persons[$row['person_id']] = new Person($row);
            }
        }

        return $this;
    }


    /**
     * Returns ticket involved persons
     *
     * @param bool $active (filter only active)
     * @param bool $groupped (group by company name)
     * @param int $personId (include when showing active, even if user is currentry inactive)
     *
     * @return PersonInterface[]
     */
    public function getPersons($groupped = false, $active = false, $personId = null, $filter = Person::FILTER_ALL)
    {
        if (!isset($this->_persons)) {
            $this->loadPersons();
        }

        $persons = array();

        if ($active) {
            if (!$groupped) {
                if ($personId) {
                    $persons = Arrays::byValue($this->_persons, 'active', 1);
                    if (!array_key_exists($personId, $persons)) {
                        $person[$personId] = $this->getPerson($personId);
                    }
                } else {
                    $persons = Arrays::byValue($this->_persons, 'active', 1);
                }
            } else {
                $persons = array();
                $persons[Lang::get('GENERAL_ADMINISTRATORS')] = [];
                $list = Arrays::byValue($this->_persons, 'active', 1);

                if ($personId && !array_key_exists($personId, $list)) {
                    $list[$personId] = $this->getPerson($personId);
                }

                foreach ($list as $person) {
                    if ($person['company_id']) {
                        $group = $person['company_short_name'] ?: $person['company_name'];
                    } else {
                        $group = Lang::get('GENERAL_ADMINISTRATORS');
                    }
                    $persons[$group][$person['person_id']] = $person;
                }
                $persons = array_filter($persons);
            }
        } else {
            if (!$groupped) {
                $persons = $this->_persons;
            } else {
                $persons = array();
                $persons[Lang::get('GENERAL_ADMINISTRATORS')] = [];
                foreach ($this->_persons as $person) {
                    if ($person['company_id']) {
                        $group = $person['company_short_name'] ?: $person['company_name'];
                    } else {
                        $group = Lang::get('GENERAL_ADMINISTRATORS');
                    }
                    $persons[$group][$person['person_id']] = $person;
                }
                $persons = array_filter($persons);
            }
        }

        if ($filter != Person::FILTER_ALL) {
            return Arrays::filterByClass($persons, $filter);
        } else {
            return $persons;
        }
    }


    /**
     * Returns ticket involved active persons
     *
     * @param bool $groupped
     * @return Velis\PersonInterface[]
     */
    public function getActivePersons($groupped = false, $personId = null)
    {
        return $this->getPersons($groupped, true, $personId);
    }


    /**
     * Returns users from persons collection
     *
     * @param bool $groupped
     * @param bool $active
     * @param int $userId
     *
     * @return User[]
     */
    public function getUsers($groupped = false, $active = false, $userId = null)
    {
        return $this->getPersons($groupped, $active, $userId, Person::FITLER_USER);
    }


    /**
     * Returns active users from persons collection
     *
     * @param bool $grouped
     * @param int  $userId
     *
     * @return User[]
     */
    public function getActiveUsers($grouped = false, $userId = null)
    {
        return $this->getUsers($grouped, true, $userId);
    }


    /**
     * Returns user from ticket
     * @param $personId
     * @return PersonInterface|void
     * @throws NoColumnsException
     * @throws \Psr\SimpleCache\InvalidArgumentException
     */
    public function getPerson($personId)
    {
        if ($personId) {
            if (!isset($this->_persons)) {
                $this->loadPersons();
            }

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

            if (array_key_exists($personId, $this->_persons)) {
                return $this->_persons[$personId];
            } elseif ($user = $userProvider->getUser($personId)) {
                return $user;
            } else {
                return Person::listById($personId);
            }
        }
    }


    /**
     * Returns true if person belongs to ticket persons
     *
     * @param PersonInterface|int $person
     * @return bool
     */
    public function hasPerson($person)
    {
        return array_key_exists(
            $person instanceof PersonInterface ? $person->id() : $person,
            $this->getPersons()
        );
    }
}
