from copy import deepcopy

from django.db.models import Q

from django_idm_api.exceptions import RoleNotFound


class BaseActions(object):
    get_objects = None

    def __init__(self, tree_node, key_crumbs):
        self.tree_node = tree_node
        self.key_crumbs = key_crumbs

    @staticmethod
    def _extract_object_fields(key, value):
        key = key.strip('_')
        name = value.pop('name')
        name_ru = name['ru'].strip('_')
        name_en = name['en'].strip('_')
        return key, name_ru, name_en

    @staticmethod
    def _extract_action_cls(value):
        return value.pop('_actions_cls', BaseActions)

    def _init_action(self, actions_cls, key, value):
        key_crumbs = self.key_crumbs[:]
        key_crumbs.append((self.tree_node['roles']['slug'], key))
        return actions_cls(tree_node=value, key_crumbs=key_crumbs)

    def get_values(self):
        values = {}
        if self.get_objects is None:
            for key, value in self.tree_node['roles']['values'].items():
                actions_cls = self._extract_action_cls(value)
                value = self._init_action(actions_cls, key, value).get_info()
                self.set_value(values, key, value)
        else:
            key_field, base_value = next(iter(self.tree_node['roles']['values'].items()))
            key_field, name_ru_field, name_en_field = self._extract_object_fields(key_field, base_value)
            actions_cls = self._extract_action_cls(base_value)

            objects = self.get_objects(fields={key_field, name_ru_field, name_en_field})
            for obj in objects:
                value = deepcopy(base_value)
                value['name'] = {'ru': obj[name_ru_field], 'en': obj[name_en_field]}
                key = str(obj[key_field])
                value = self._init_action(actions_cls, key, value).get_info()
                self.set_value(values, key, value, obj=obj)

        return values

    def set_value(self, values, key, value, obj=None):
        values[key] = value

    def get_info(self):
        if 'roles' in self.tree_node:
            values = self.get_values()
            self.tree_node['roles']['values'] = values
        return self.tree_node

    def _get_next_action(self, role):
        assert 'roles' in self.tree_node, 'Broken role tree'

        slug = self.tree_node['roles']['slug']
        value_key = role[slug]

        if len(self.tree_node['roles']['values']) == 1:
            key, value = next(iter(self.tree_node['roles']['values'].items()))
        else:
            key = value_key
            value = self.tree_node['roles']['values'].get(value_key)

        if value is None or not (key == value_key or key.startswith('__')):
            raise RoleNotFound('Role "%s:%s" not found' % (slug, value_key))

        actions_cls = self._extract_action_cls(value)
        return self._init_action(actions_cls, key, value)

    def add_role(self, person_id, role):
        action = self._get_next_action(role)
        return action.add_role(person_id, role)

    def remove_role(self, person_id, role):
        action = self._get_next_action(role)
        return action.remove_role(person_id, role)

    def get_all_roles(self):
        assert 'roles' in self.tree_node, 'Broken role tree'

        items = self.tree_node['roles']['values'].items()

        for key, value in items:
            actions_cls = self._extract_action_cls(value)
            action = self._init_action(actions_cls, key, value)
            for role in action.get_all_roles():
                yield role


def filter_q_factory(filter_dict):
    def filter_q(prefix=None):
        f_dict = filter_dict.copy()
        if prefix:
            for key in list(f_dict):
                value = f_dict.pop(key)
                key = '%s__%s' % (prefix, key)
                f_dict[key] = value

        return Q(**f_dict)
    return filter_q
