import attr
from collections import OrderedDict
from typing import List, Optional

from django.db.models import Q, Count

from staff.departments.models import (
    DepartmentRoles,
    ValuestreamRoles,
    Department,
    DepartmentStaff,
    InstanceClass,
)
from staff.lib.utils.qs_values import localize, extract_related
from staff.person.models import Staff

from staff.headcounts.headcounts_summary import HeadcountsSummaryCalculator, DepartmentCounters
from staff.headcounts.permissions import Permissions


@attr.s(auto_attribs=True)
class DepartmentChiefProperties:
    login: str
    first_name: str
    last_name: str


@attr.s(auto_attribs=True)
class DepartmentProperties:
    url: str
    name: str
    instance_class: str


@attr.s(auto_attribs=True)
class DepartmentSummary:
    id: int
    department: DepartmentProperties
    headcounts: DepartmentCounters
    headcounts_with_children: DepartmentCounters
    overdraft_percents_with_child: str
    chief: Optional[DepartmentChiefProperties]
    child_departments: List = attr.ib(factory=list)


@attr.s(auto_attribs=True)
class DepartmentsSummary:
    departments: List[DepartmentSummary]


class HeadcountsSummaryModel:
    """
    The model is responsible for managing the data of the application.
    Do not mess with django's so called "models" ))
    """

    _ROLES = (
        DepartmentRoles.CHIEF.value,
        DepartmentRoles.HR_PARTNER.value,
        DepartmentRoles.HR_ANALYST.value,
        ValuestreamRoles.HEAD.value,
    )

    def __init__(self, observer: Staff, move_department_with_same_chief_to_parent_level: bool, lang: str = 'ru'):
        self.move_department_with_same_chief_to_parent_level = move_department_with_same_chief_to_parent_level
        self.permissions = Permissions(observer)
        self.lang = lang

    def departments_summary_for_person(self, person: Staff, valuestream_mode: bool) -> DepartmentsSummary:
        subordinate_deps = (
            DepartmentStaff.objects
            .filter(staff=person, role_id__in=self._ROLES)
            .values('department_id')
        )
        departments_filter_conditions = Q(id__in=subordinate_deps) | Q(parent_id__in=subordinate_deps)

        instance_class = InstanceClass.VALUESTREAM.value if valuestream_mode else InstanceClass.DEPARTMENT.value
        departments_filter_conditions &= Q(instance_class=instance_class)

        return self._departments_summary(departments_filter_conditions)

    def departments_summary_for_department(self, department_url: str, show_nested: bool) -> DepartmentSummary:
        departments_filter_conditions = Q(url=department_url)
        if show_nested:
            departments_filter_conditions |= Q(parent__url=department_url)
        result = self._departments_summary(departments_filter_conditions).departments[0]
        return result

    def _departments_summary(self, departments_filter_conditions) -> DepartmentsSummary:
        departments = self._departments_summary_qs(departments_filter_conditions)

        self._fill_headcounts_data(departments)
        result = self._prepare_result(departments)
        return result

    def _fill_headcounts_data(self, departments):
        if not departments:
            return

        departments_ids = [department['id'] for department in departments]
        valuestream_mode = departments[0]['instance_class'] == InstanceClass.VALUESTREAM.value
        summary_calculator = HeadcountsSummaryCalculator(valuestream_mode=valuestream_mode)
        headcounts_data = summary_calculator.get_summary_for_departments(departments_ids, False)
        headcounts_data_with_children = summary_calculator.get_summary_for_departments(departments_ids, True)

        for department in departments:
            department['headcounts'] = headcounts_data.get(department['id'], DepartmentCounters())
            department['headcounts_with_children'] = headcounts_data_with_children.get(
                department['id'], DepartmentCounters()
            )

    def _departments_summary_qs(self, base_departments_filter):
        department_fields = (
            'id',
            'url',
            'name',
            'name_en',
            'parent_id',
            'lft',
            'rght',
            'tree_id',
            'instance_class',
        )

        departments = (
            Department.all_types
            .annotate(num_positions=Count('positions'))
            .filter(Q(intranet_status=1) | Q(num_positions__gt=0))
            .filter(base_departments_filter)
            .order_by('lft')
            .distinct()
            .values(*department_fields)
        )

        departments = self.permissions.filter_by_observer(departments)

        return departments

    @staticmethod
    def _chiefs(departments):
        role_fields = (
            'department_id',
            'staff__login',
            'staff__first_name',
            'staff__last_name',
            'staff__first_name_en',
            'staff__last_name_en',
        )

        roles = (
            DepartmentStaff.objects
            .filter(department_id__in={d['id'] for d in departments})
            .filter(role_id__in=(DepartmentRoles.CHIEF.value, ValuestreamRoles.HEAD.value))
            .values(*role_fields)
        )

        result = {
            role['department_id']: localize(extract_related(role, 'staff'))
            for role in roles
        }

        return result

    def _prepare_result(self, departments: List) -> DepartmentsSummary:
        chiefs_for_departments = HeadcountsSummaryModel._chiefs(departments)
        result: OrderedDict = OrderedDict()

        for department in departments:
            result_department_data = DepartmentSummary(
                id=department['id'],
                department=DepartmentProperties(
                    url=department['url'],
                    name=department['name'] if self.lang == 'ru' else department['name_en'],
                    instance_class=department['instance_class'],
                ),
                overdraft_percents_with_child=department['headcounts'].overdraft_percents_with_child,
                headcounts=department['headcounts'],
                headcounts_with_children=department['headcounts_with_children'],
                chief=chiefs_for_departments.get(department['id']),
            )

            if self._should_add_department_to_parent_level(department, result, chiefs_for_departments):
                result[department['id']] = result_department_data
            else:
                result[department['parent_id']].child_departments.append(result_department_data)

        return DepartmentsSummary(departments=list(result.values()))

    def _should_add_department_to_parent_level(self, department, result, chiefs_for_departments):
        parent_id = department['parent_id']
        department_id = department['id']

        if parent_id not in result:
            return True

        if self.move_department_with_same_chief_to_parent_level:
            #  If child has same chief as parent we have to put it on top level
            return self._departments_has_same_chief(chiefs_for_departments, parent_id, department_id)

        return False

    @staticmethod
    def _departments_has_same_chief(chiefs_for_departments, first_department_id, second_department_id):
        first_department_chief = chiefs_for_departments.get(first_department_id)
        second_department_chief = chiefs_for_departments.get(second_department_id)

        if not first_department_chief or not second_department_chief:
            return False

        return first_department_chief['login'] == second_department_chief['login']
