# coding: utf-8


import logging

import collections
from django.conf import settings

from idm.core.constants.groupmembership import GROUPMEMBERSHIP_STATE
from idm.nodes.fetchers import IDSFetcher
from idm.users import ranks
from idm.users.canonical import (CanonicalGroup, CanonicalMember,
                                 CanonicalGroupResponsibility as CanonicalResponsibility)

log = logging.getLogger(__name__)


def discard_parent_members(parent_canonical, canonical):
    parent_canonical.members = {
        key: member for key, member in parent_canonical.members.items()
        if key not in canonical.members.keys()
    }


def drop_dismissed(items, flat=False):
    for item in items:
        person = item.get('person')
        if person is None:
            continue
        if flat:
            if person['is_dismissed'] is False:
                yield item
        else:
            if person['official']['is_dismissed'] is False:
                yield item


class IDSGroupFetcher(IDSFetcher):
    STAFF_RANK_TO_IDM_RANK = {
        'chief': ranks.HEAD,
        'deputy': ranks.DEPUTY,
        'hr_partner': ranks.HR_PARTNER,
        'budget_holder': ranks.BUDGET_HOLDER,
        'general_director': ranks.GENERAL_DIRECTOR,
    }

    ROLE_ID_TO_RANK = {
        settings.IDM_PLANNER_HEAD_ROLE: ranks.HEAD,
        settings.IDM_PLANNER_DEPUTY_ROLE: ranks.DEPUTY,
        settings.IDM_PLANNER_RESPONSIBLE_ROLE: ranks.RESPONSIBLE,
    }

    department_fields = (
        'department.heads.role',
        'department.heads.person.id',
        'department.heads.person.login',
        'department.heads.person.official.is_dismissed',
        'department.name.full',
    )

    service_fields = (
        'service.id',
        'responsibles.person.id',
        'responsibles.person.official.is_dismissed',
    )

    service_role_fields = (
        'parent.id',
        'parent.service.id',
        'role_scope',
    )

    wiki_fields = (
        'responsibles.person.id',
        'responsibles.person.official.is_dismissed',
    )

    def get_lookup(self, node_type, additional_fields=None):
        lookup = {
            'type': node_type,
            'is_deleted': False,
            '_fields': 'id,url,name,parent.id,description,is_deleted',
            '_limit': self.page_size,
            '_sort': 'id',
        }
        if additional_fields is not None:
            lookup['_fields'] += ',' + ','.join(additional_fields)
        return lookup

    def path_from_node_data(self, group_data):
        path = [ancestor['id'] for ancestor in group_data.get('ancestors', [])] + [group_data['id']]
        return path

    def fetch(self, node=None):
        group_data = None
        if node.type == 'department':
            group_data = self.fetch_departments(node)
        elif node.type == 'wiki':
            group_data = self.fetch_wiki(node)
        elif node.type == 'service':
            group_data = self.fetch_services(node)
        tree_data = self.treeify_data(group_data, node.type)
        return tree_data

    def fetch_memberships(self, group_type):
        membership_lookup = {
            'group.type': group_type,
            'person.official.is_dismissed': False,
            '_fields': ','.join([
                'id',
                'person.id',
                'person.official.is_dismissed',
                'group.id'
            ]),
            '_limit': self.page_size,
            '_sort': 'id',
        }
        log.info('Fetching %s groups memberships. IDS lookup: %s', group_type, membership_lookup)
        groups = collections.defaultdict(dict)
        while True:
            membership_iterator = self.staff_memberships_repo.getiter(membership_lookup)
            page = list(membership_iterator.first_page)
            if not page:
                break
            membership_lookup['_query'] = 'id>%d' % page[-1]['id']
            for membership in drop_dismissed(page):
                group_id = membership['group']['id']
                member = CanonicalMember(
                    staff_id=membership['person']['id'],
                    state=GROUPMEMBERSHIP_STATE.ACTIVE,
                )
                groups[group_id][member.as_key()] = member
        return groups

    def fetch_departments(self, node):
        lookup = self.get_lookup(node.type, self.department_fields)
        log.info('Fetching department groups. IDS lookup: %s', lookup)
        group_iterator = self.staff_groups_repo.getiter(lookup)
        groups = []

        for group_data in group_iterator:
            department_info = group_data.get('department', {})
            department_heads = department_info.get('heads', ())
            names = department_info.get('name', {}).get('full', {})
            responsibles = {}
            for head in drop_dismissed(department_heads):
                rank = self.STAFF_RANK_TO_IDM_RANK.get(head['role'])
                if rank is None:
                    continue
                responsibility = CanonicalResponsibility(staff_id=head['person']['id'], rank=rank)
                responsibles[responsibility.as_key()] = responsibility

            groups.append(CanonicalGroup(
                hash='',
                type='department',
                external_id=group_data['id'],
                parent_id=group_data.get('parent', {}).get('id'),
                slug=group_data['url'],
                description=group_data.get('description', ''),
                name=names.get('ru') or group_data['name'],
                name_en=names.get('en') or group_data['name'],
                members={},
                responsibles=responsibles,
            ))
        return groups

    def fetch_services(self, node):
        # Сервисы
        # https://staff-api.yandex-team.ru/v3/groups?type=service&url=svc_rules&_fields=_all
        lookup = self.get_lookup(node.type, self.service_fields)
        log.info('Fetching service groups. IDS lookup: %s', lookup)
        group_iterator = self.staff_groups_repo.getiter(lookup)
        groups = {}
        group_url_to_id = {}
        services = {}
        scope_to_group = {}  # service_id -> scope -> servicerole_dict

        memberships_service = self.fetch_memberships('service')
        memberships_servicerole = self.fetch_memberships('servicerole')

        for group_data in group_iterator:
            responsibles = [
                CanonicalResponsibility(staff_id=person['person']['id'], rank=ranks.HEAD)
                for person in drop_dismissed(group_data.get('responsibles', []))
            ]
            canonical = CanonicalGroup(
                hash='',
                type='service',
                external_id=group_data['id'],
                parent_id=group_data.get('parent', {}).get('id'),
                slug=group_data['url'],
                description=group_data.get('description', ''),
                name=group_data['name'],
                name_en=group_data['name'],
                members=memberships_service.get(group_data['id'], {}),
                responsibles={responsible.as_key(): responsible for responsible in responsibles},
            )
            groups[group_data['id']] = canonical
            group_url_to_id[group_data['url']] = canonical.external_id
            services[group_data['service']['id']] = canonical
            scope_to_group[group_data['service']['id']] = {}

        # Сервисные роли
        # https://staff-api.yandex-team.ru/v3/groups?type=servicerole&url=svc_rules_development&_fields=_all
        lookup = self.get_lookup('servicerole', self.service_role_fields)
        log.info('Fetching servicerole groups. IDS lookup: %s', lookup)
        group_iterator = self.staff_groups_repo.getiter(lookup)
        for group_data in group_iterator:
            parent_id = group_data.get('parent', {}).get('id', None)
            if parent_id not in groups:
                continue
            canonical = CanonicalGroup(
                hash='',
                type='service',
                external_id=group_data['id'],
                parent_id=parent_id,
                slug=group_data['url'],
                description=group_data.get('description', ''),
                name=group_data['name'],
                name_en=group_data['name'],
                members=memberships_servicerole.get(group_data['id'], {}),
                responsibles={},
            )
            groups[group_data['id']] = canonical
            discard_parent_members(groups[parent_id], canonical)
            group_url_to_id[group_data['url']] = group_data['id']
            if group_data['role_scope']:
                service_id = group_data['parent']['service']['id']
                scope_to_group[service_id][group_data['role_scope']] = group_data

        # Ответственные за сервисные роли, участники встроенных ролей
        # https://abc-back.yandex-team.ru/api/v3/services/members/?service__slug=rules
        responsible_lookup = {
            'fields': ','.join([
                'service.id',
                'service.slug',
                'person.id',
                'role.id',
            ]),
            'role': list(self.ROLE_ID_TO_RANK.keys()),
            'page_size': settings.IDM_ABC_PAGE_SIZE,
        }
        log.info('Fetching responsibles. IDS lookup: %s', responsible_lookup)
        members_iterator = self.abc_members_repo.getiter(responsible_lookup)
        for member_data in members_iterator:
            service_url = 'svc_%s' % member_data['service']['slug'].lower()
            service_group = services.get(member_data['service']['id'])
            # в тестинге данные могут быть рассинхронизированы
            if service_group is None:
                continue

            person_id = member_data['person']['id']
            try:
                member_role_id = member_data['role']['id']
                if not member_role_id:
                    continue
                if service_url in group_url_to_id:
                    responsibility = CanonicalResponsibility(
                        staff_id=person_id,
                        rank=self.ROLE_ID_TO_RANK[member_role_id]
                    )
                    service_group.responsibles[responsibility.as_key()] = responsibility
            except KeyError:
                log.error('Некорректные данные от abc: %r', member_data)
                raise
        return list(groups.values())

    def fetch_wiki(self, node):
        lookup = self.get_lookup(node.type, self.wiki_fields)
        log.info('Fetching wiki groups. IDS lookup: %s', lookup)
        group_iterator = self.staff_groups_repo.getiter(lookup)
        group_dict = {}
        memberships = self.fetch_memberships('wiki')

        for group_data in group_iterator:
            responsibles = [
                CanonicalResponsibility(staff_id=person['person']['id'], rank=ranks.HEAD)
                for person in drop_dismissed(group_data.get('responsibles', ()))
            ]
            group_dict[group_data['id']] = CanonicalGroup(
                hash='',
                type='wiki',
                external_id=group_data['id'],
                parent_id=group_data.get('parent', {}).get('id'),
                slug=group_data['url'],
                description=group_data.get('description', ''),
                name=group_data['name'],
                name_en=group_data['name'],
                members=memberships.get(group_data['id'], {}),
                responsibles={responsible.as_key(): responsible for responsible in responsibles},
            )

        groups = list(group_dict.values())
        return groups

    def get_root(self, children, item_type=None):
        return CanonicalGroup(
            hash='',
            external_id=0,
            parent_id=None,
            type=item_type,
            slug=item_type,
            description='',
            name='',
            children=children
        )
