from django.db.models import Count
from staff.proposal.controllers.department import order_field

from staff.lib.utils.qs_values import localize
from staff.lib.models.departments_chain import get_departments_tree

from staff.departments.tree_lib.abstract_entity_info import AbstractEntityInfo
from staff.departments.tree.forms import split


class TreeBuilder:
    def __init__(self, info_provider: AbstractEntityInfo):
        self.info_provider = info_provider

    def _base_clean(self, dep):
        dep = localize(dep)

        tech_fields = [
            'tree_id',
            'lft',
            'rght',
            'parent_id',
        ]
        for field in tech_fields:
            del dep[field]

        return dep

    def _short_clean(self, dep):
        dep = self._base_clean(dep)
        full_fields = [
            'wiki_page',
            'maillists',
            'clubs',
            'description',
        ]
        for field in full_fields:
            del dep[field]

        return dep

    def _clean(self, dep):
        dep = self._base_clean(dep)

        info = dep.setdefault('info', {})

        # TODO ниже должны остаться только split(), вся логика на сохранении
        info['wiki_page'] = dep.pop('wiki_page').strip(' /')
        if not info['wiki_page']:
            del info['wiki_page']

        info['maillists'] = split(dep.pop('maillists'))
        if not info['maillists']:
            del info['maillists']

        info['clubs'] = split(dep.pop('clubs'))
        if not info['clubs']:
            del info['clubs']
        # --------------------

        info['description'] = dep.pop('description').strip()
        if not info['description']:
            del info['description']

        return dep

    def _get_descendants_qty(self, all_ids):
        """Наличие вложенных подразделений"""
        descendants_qty = {
            p['parent_id']: p['qty']
            for p in (
                self.info_provider.departments_query()
                .filter(parent_id__in=all_ids)
                .values('parent_id')
                .annotate(qty=Count('id'))
            )
        }
        return descendants_qty

    def _fill_order_field(self, departments):
        for dep in departments:
            dep['order_field'] = order_field(dep['tree_id'], dep['lft'])

    def _fill_chains(self, departments):
        departments_chains = self.get_departments_chains(departments)

        for dep in departments:
            dep['chain'] = departments_chains[dep['id']]

    def _fill_counters(self, all_ids, departments):
        descendants_qty_map = self._get_descendants_qty(all_ids)

        for dep in departments:
            if dep['id'] in descendants_qty_map:
                dep['has_descendants'] = 1

        self.info_provider.fill_counters(all_ids, departments)

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

        return self.info_provider.filter_result(departments)

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

        all_ids = set(d['id'] for d in departments)
        return self.info_provider.filter(all_ids, departments)

    def _get_as_tree(self, departments, clean_func):
        # Сортируем для создания дерева
        departments = sorted(
            departments,
            key=lambda d: (d['tree_id'], d['lft'])
        )

        dictionary = {}
        result = []
        for dep in departments:
            dictionary[dep['id']] = dep
            if dep['parent_id'] in dictionary:
                (
                    dictionary[dep['parent_id']]
                    .setdefault('descendants', [])
                    .append(dep)
                )
            else:
                result.append(dep)

            dep = clean_func(dep)

        return result

    def get_as_short_tree(self, departments):
        if not departments:
            return []
        all_ids = [d['id'] for d in departments]
        self._fill_order_field(departments)
        self._fill_chains(departments)
        self._fill_counters(all_ids, departments)
        self.info_provider.fill_dep_attrs(all_ids, departments)
        self.info_provider.fill_info(all_ids, departments, info_map={})
        departments = self._filter_result(departments)
        return self._get_as_tree(departments, self._short_clean)

    def get_as_tree(self, departments, persons_map=None):
        if not departments:
            return []
        all_ids = [d['id'] for d in departments]
        self._fill_order_field(departments)
        self._fill_chains(departments)
        self._fill_counters(all_ids, departments)
        self.info_provider.fill_dep_attrs(all_ids, departments)
        self.info_provider.fill_info(all_ids, departments, persons_map)
        departments = self._filter_result(departments)
        return self._get_as_tree(departments, self._clean)

    def get_for_info_list(self, departments, info_map):
        if not departments:
            return []
        all_ids = [d['id'] for d in departments]
        self._fill_order_field(departments)
        self._fill_chains(departments)
        self._fill_counters(all_ids, departments)
        self.info_provider.fill_dep_attrs(all_ids, departments)
        self.info_provider.fill_info(all_ids, departments, info_map)
        return [self._short_clean(dep) for dep in departments]

    def get_as_short_list(self, departments):
        if not departments:
            return []
        all_ids = [d['id'] for d in departments]
        self._fill_order_field(departments)
        self._fill_chains(departments)
        self._fill_counters(all_ids, departments)
        departments = self._filter_result(departments)
        return [self._short_clean(dep) for dep in departments]

    def get_as_list(self, departments, persons_map=None):
        if not departments:
            return []
        all_ids = [d['id'] for d in departments]
        self._fill_order_field(departments)
        self._fill_chains(departments)
        self._fill_counters(all_ids, departments)
        self.info_provider.fill_dep_attrs(all_ids, departments)
        self.info_provider.fill_info(all_ids, departments, persons_map)
        departments = self._filter_result(departments)
        return [self._clean(dep) for dep in departments]

    def get_departments_chains(self, departments):
        department_ids = [d['id'] for d in departments]
        fields = ('id', 'url', 'name')
        result = get_departments_tree(department_ids, fields)

        def clean_func(d):
            return {
                field: value
                for field, value in localize(d).items()
                if field in fields
            }

        result = {
            department_id: [clean_func(item) for item in ancestors_chain]
            for department_id, ancestors_chain in result.items()
        }

        return result
