from itertools import chain

from django.conf import settings
from django.db.models import Q

from staff.departments.models import Department, DepartmentStaff, DepartmentRoles

from staff.person.models import Staff


class StaffService(object):
    """
    Класс, предоставляющий информацию, связанную с сотрудником
    """
    def __init__(self, staff):
        self.staff = staff
        self._departments = None

    def department_chain(self):
        """
        Подразделение сотрудника и все вышележащие, начиная с верхних
        """
        if self._departments is None:
            self._departments = self.staff.departments
        return self._departments

    def get_chief(self):
        """
        Получить руководителя для сотрудника, если он есть
        """
        try:
            return self.get_chiefs(depth=1).get()
        except Staff.DoesNotExist:
            return None

    def get_chiefs(self, depth=None, level_gte=0):
        """
        Руководители подразделения сотрудника и всех вышележащих подразделений,
        исключая самого сотрудника, начиная с самого маленького
        """
        return Staff.objects.filter(
                departmentstaff__role_id=DepartmentRoles.CHIEF.value,
                departmentstaff__department__in=self.department_chain(),
                departmentstaff__department__level__gte=level_gte)\
            .exclude(id=self.staff.pk)\
            .order_by('-departmentstaff__department__level')[:depth]

    def get_deputies(self):
        """
        Заместители руководителей подразделения сотрудника и всех вышележащих
        подразделений, исключая самого сотрудника, начиная с самого маленького
        """
        return (
            Staff.objects
            .filter(
                departmentstaff__role_id=DepartmentRoles.DEPUTY.value,
                departmentstaff__department__in=self.department_chain()
            )
            .exclude(pk=self.staff.pk)
            .order_by('-departmentstaff__department__level')
        )

    def get_responsibles(self):
        """
        Заместители, ответственные за подразделение сотрудника и все
        вышележащие, исключая самого сотрудника, начиная с самого маленького
        """
        return (
            Staff.objects
            .filter(
                departmentstaff__departmentresponsible__isnull=False,
                departmentstaff__departmentresponsible__responsible_for__in=self.department_chain(),
            )
            .exclude(pk=self.staff.pk)
            .order_by('department__level')
        )

    def get_superiors(self):
        """
        Генератор, который выдает руководителей, заместителей и ответственных.
        Отвественные могут попасть в выдачу два раза (как заместители и как
        ответственные за одно из подразделений в цепочке)
        """
        superiors_ids = (
            list(self.get_chiefs().values_list('id', flat=True)) +
            list(self.get_deputies().values_list('id', flat=True)) +
            list(self.get_responsibles().values_list('id', flat=True))
        )
        return Staff.objects.filter(pk__in=superiors_ids)

    def get_chief_subordinates(self):
        """
        Подчиненные сотрудника в качестве руководителя
        """
        if not self.staff.is_big_boss:
            return Staff.objects.none()
        else:
            chief_departments = Department.objects.filter(
                departmentstaff__role_id=DepartmentRoles.CHIEF.value,
                departmentstaff__staff=self.staff)
            departments_under_control_ids = set(
                chief_departments.values_list('id', flat=True))
            for department in chief_departments:
                departments_under_control_ids.update(
                    department.children.values_list('id', flat=True))

            return (
                Staff.objects
                .filter(is_dismissed=False, department__in=departments_under_control_ids)
                .exclude(pk=self.staff.pk)
            )

    def get_deputy_subordinates(self):
        """
        Подчиненные сотрудника в качестве заместителя
        """
        deputy_departments = Department.objects.filter(
            departmentstaff__role_id=DepartmentRoles.DEPUTY.value,
            departmentstaff__departmentresponsible__isnull=True,
            departmentstaff__staff=self.staff)

        departments_under_control_ids = set(deputy_departments.values_list('id', flat=True))
        for department in deputy_departments:
            departments_under_control_ids.update(department.children.values_list('id', flat=True))

        return (
            Staff.objects
            .filter(is_dismissed=False, department__in=departments_under_control_ids)
            .exclude(pk=self.staff.pk)
            .exclude(
                departmentstaff__role_id=DepartmentRoles.CHIEF.value,
                departmentstaff__department__in=deputy_departments,
            )
        )

    def get_responsible_subordinates(self):
        """
        Подчиненные сотрудника в качестве ответственного заместителя
        """
        responsible_departments = Department.objects.filter(
            responsibles__staff=self.staff)
        departments_under_control_ids = set(
            responsible_departments.values_list('id', flat=True))
        for department in responsible_departments:
            departments_under_control_ids.update(
                department.children.values_list('id', flat=True))

        return (
            Staff.objects
            .filter(is_dismissed=False, department__in=departments_under_control_ids)
            .exclude(pk=self.staff.pk)
            .exclude(
                departmentstaff__role_id=DepartmentRoles.CHIEF.value,
                departmentstaff__department__in=responsible_departments,
            )
        )

    def get_subordinates(self):
        """
        Генератор, который выдает подчиненных.
        """
        return chain(
            self.get_chief_subordinates(),
            self.get_deputy_subordinates(),
            self.get_responsible_subordinates())

    def get_direction(self):
        """Return the Direction an employee belongs to."""
        try:
            return (
                self.staff.department.get_ancestors()
                    .filter(kind_id=settings.DIS_DIRECTION_KIND_ID)[0]
            )
        except IndexError:
            return None


def get_chiefs_role_bulk(staff_list, level_gte=None, level_lte=None):
    """
    Возвращает dict в котором ключи - переданные сотрудники,
    а в значениях массивы ролей руководителей начиная с младшего.
    Для оптимальной работы при получении списка сотрудников
    следует выполнить select_related('department'), так как
    в функции используются атрибуты департамента сотрудника.
    """
    q = Q()
    for person in staff_list:
        q |= Q(
            department__lft__lte=person.department.lft,
            department__rght__gte=person.department.rght,
            department__tree_id=person.department.tree_id,
        )

    if not q:
        return {}

    roles_qs = (
        DepartmentStaff.objects.filter(q)
        .filter(role_id=DepartmentRoles.CHIEF.value)
        .order_by('-department__level')
        .select_related('department', 'staff')
    )

    if level_gte is not None:
        roles_qs = roles_qs.filter(department__level__gte=level_gte)

    if level_lte is not None:
        roles_qs = roles_qs.filter(department__level__lte=level_lte)

    result = dict((s, []) for s in staff_list)

    for role in roles_qs.iterator():
        for staff in staff_list:
            if staff != role.staff and is_ancestor(role.department, staff.department):
                result[staff].append(role)

    return result


def get_chiefs_bulk(staff_list, level_gte=None, level_lte=None):

    chiefs_role = get_chiefs_role_bulk(staff_list, level_gte, level_lte)

    for roles in chiefs_role.values():
        for index, role in enumerate(roles):
            roles[index] = role.staff

    return chiefs_role


def is_ancestor(parent, child):
    return (parent.lft <= child.lft and
            parent.rght >= child.rght and
            parent.tree_id == child.tree_id)
