# coding: utf-8


import itertools

from django.utils.encoding import force_text
from django.utils.translation import ugettext as _

from idm.core.constants.workflow import DEFAULT_PRIORITY
from idm.core.workflow.exceptions import WorkflowError, InvalidPriorityError, ApproverNotFoundError
from idm.core.workflow.plain.user import UserWrapper
from idm.framework.mixins import Representable
from idm.users.models import User


def flatten_approverify(approvers):
    for approver in approvers:
        if isinstance(approver, Approver):
            yield approver
        elif isinstance(approver, AnyApprover):
            for other_approver in approver.approvers:
                yield other_approver
        else:
            yield Approver(approver)


def approverify(approver):
    """Функция возвращает либо объект типа Approver, либо объект типа
    AnyApprover. При этом на вход может принимать объекты типа:
    Approver, AnyApprover, User, UserWrapper и basestring. В первых двух случаях
    функция возвращает тот же объект, что был передан на входе. В остальных -
    создает новый объект класса Approver."""
    if isinstance(approver, (Approver, AnyApprover)):
        return approver
    return Approver(approver)


def combine_notifications(*notifications):
    result = None
    for notify in notifications:
        if notify is True:
            return True
        elif notify is False:
            result = False
    return result


def load_users(flat_approvers):
    missing_users = [
        approver.username for approver in flat_approvers if not approver._user
    ]
    undefined_users = []
    users = []
    if missing_users:
        users = User.objects.users().filter(username__in=missing_users).all()
    users_dict = {user.username: user for user in users}
    for approver in flat_approvers:
        if approver._user:
            continue
        user = users_dict.get(approver.username)
        if user:
            approver._user = user
        else:
            undefined_users.append(approver.username)
    if undefined_users:
        undefined_users_str = ", ".join(undefined_users)
        raise ApproverNotFoundError(
            _('Подтверждающие не найдены: %s') % undefined_users_str, undefined_users_str
        )


class Approver(Representable):
    """Класс для представления аппрувера роли.
    Аппрувером может выступать только конкретный человек.
    Предполагается, что аппруверов можно объединять логикой ИЛИ:

    >>> Approver('frodo') | Approver('legolas') | Approver('gandalf')

    Такая конструкция объединяется в один объект AnyApprover.
    """

    repr_template = '%s%s'
    class_name_repr = 'approver'

    def __init__(self, username: str, notify: bool = None, priority: int = DEFAULT_PRIORITY):
        # берем приоритет из UserWrapper если пользователь не задал приоритет извне
        self._user = None
        if isinstance(username, UserWrapper) and priority == DEFAULT_PRIORITY:
            self.priority = username.priority
        else:
            self.priority = priority

        if isinstance(username, User):
            self._user = username
            self.username = self._user.username
        else:
            if hasattr(username, 'username'):
                username = username.username
            if isinstance(username, str):
                self.username = username
            else:
                raise WorkflowError(_('Неизвестный аргумент функции approver(): %s') % force_text(username))
        self.notify = notify

    def __str__(self):
        if self.notify is not None:
            return "({}, priority={}, notify={})".format(self.user.username, self.priority, self.notify)
        else:
            return "({}, priority={})".format(self.user.username, self.priority)

    def __hash__(self):
        return hash(self.username)

    def __eq__(self, other):
        if isinstance(other, Approver):
            return self.username == other.username
        elif isinstance(other, UserWrapper):
            return self.username == other._user.username
        elif isinstance(other, User):
            return self.username == other.username

    def __ne__(self, other):
        return not (self == other)

    def __or__(self, other):
        return AnyApprover([self, other])

    def __iter__(self):
        """Возвращает Iterable по пользователям."""
        yield self

    def __len__(self):
        return 1

    @property
    def user(self):
        if not self._user:
            try:
                self._user = User.objects.users().get(username=self.username)
            except User.DoesNotExist:
                raise ApproverNotFoundError(_('Подтверждающие не найдены: %s') % self.username, self.username)
        return self._user

    @property
    def approvers(self):
        return [self]

    @property
    def priority(self):
        return self._priority

    @priority.setter
    def priority(self, priority):
        if not isinstance(priority, int) or priority < 0 or priority > DEFAULT_PRIORITY:
            raise InvalidPriorityError(_("Приоритет должен быть целым числом от 0 до {}".format(DEFAULT_PRIORITY)))

        self._priority = priority


class AnyApprover(Representable):
    """Класс для представления списка людей, каждый из которых может быть аппрувером.

    Достаточно, чтобы добавление роли подтвердил любой из перечисленных в этом списке.
    Важно помнить, что для аппруверов с правилом ИЛИ - письмо в функции pending_approve_requests
    уходит лишь первому доступному.
    """

    repr_template = '%s%s'
    class_name_repr = 'any_from'
    flatten_func = staticmethod(flatten_approverify)

    def __init__(self, approvers, notify=None, priority=DEFAULT_PRIORITY):
        self.approvers = []
        self.approver_dict = {}
        self.priority = priority
        self._add_approvers(approvers, notify)

    def __or__(self, other):
        # приоритет группы больше не нужен
        self.priority = DEFAULT_PRIORITY
        if isinstance(other, Approver):
            self._add_approvers(other, notify=other.notify)
            return self
        elif isinstance(other, AnyApprover):
            # проставить новый приоритет сотрудникам без приоритета
            self._add_approvers(other.approvers)
            return self
        elif isinstance(other, (str, UserWrapper)):
            self._add_approvers([other])
            return self
        return NotImplemented

    def _add_approvers(self, approvers, notify=None):
        for approver in self.flatten_func(approvers):

            # если у approverа не выставлен приоритет
            if self.priority != DEFAULT_PRIORITY and approver.priority == DEFAULT_PRIORITY:
                approver.priority = self.priority

            if approver.username in self.approver_dict:
                existing_approver = self.approver_dict[approver.username]
                existing_approver.notify = combine_notifications(existing_approver.notify, approver.notify)
                # выбирать минимальный приоритет у повторяющихся сотрудников
                existing_approver.priority = min(existing_approver.priority, approver.priority)
            else:
                if notify is not None:
                    approver.notify = notify
                self.approvers.append(approver)
                self.approver_dict[approver.username] = approver

    def __str__(self):
        return "([{}])".format(','.join(repr(approver) for approver in self.approvers))

    def __hash__(self):
        return hash(str(self))

    def __eq__(self, other):
        if hasattr(other, '__iter__'):
            return all(our_approver == their_approver for our_approver, their_approver in
                        itertools.zip_longest(self, other))
        return NotImplemented

    def __ne__(self, other):
        return not (self == other)

    def __iter__(self):
        """Возвращает Iterable по всем login."""
        for approver in self.approvers:
            yield approver

    def __len__(self):
        return len(self.approvers)

    @property
    def user(self):
        if len(self) == 1:
            return self.approvers[0].user
        else:
            raise AttributeError('"AnyApprover" object has no attribute "user"')

    @property
    def priority(self):
        return self._priority

    @priority.setter
    def priority(self, priority):
        if not isinstance(priority, int) or priority < 0 or priority > DEFAULT_PRIORITY:
            raise InvalidPriorityError(_("Приоритет должен быть целым числом от 0 до {}".format(DEFAULT_PRIORITY)))

        self._priority = priority
