from typing import Callable
from staff.lib import waffle
from staff.person_profile.permissions.properties import Properties


class BlockBase(object):
    """
    Общая часть для всех правил.
    show_on - подмножество TARGET_TYPES - на каких анкетах вообще показывать элемент
    chief - показывать руководителю
    owner - показывать владельцу
    permission - показывать элемент, если есть эта строка в permissions
    waffle - имя вафл-флага, значение True которого отключает показывание элемента
    custom_rule - кастомная ф-я от self, properties - позволяет задать более сложные правила

    external_owner - Если True то вн. сотрудник с правом can_view_profile будет видеть блок на себе,
        но не будет на остальных
    external_other - Если True то вн. сотрудник с правами can_view_department на подразделения будет видеть блок
    """
    def __init__(self, show_on, chief=False, owner=False, permission='',
                 waffle='', custom_rule=None, not_readonly=False,
                 rule_for_external=None, external_owner=False, external_other=False):
        self.show_on = show_on
        self.waffle = waffle
        self.custom_rule: Callable[[Properties], bool] = custom_rule
        self.permission = permission
        self.not_readonly = not_readonly
        self.rule_for_external = rule_for_external
        self.external_owner = external_owner
        self.external_other = external_other

        self.checkers = []
        if owner:
            self.checkers.append(self.check_owner)
        if chief:
            self.checkers.append(self._check_chief)
        if permission:
            self.checkers.append(self.check_permission)
        if custom_rule:
            self.checkers.append(self._check_custom_rule)

    def _check_custom_rule(self, properties: Properties, target_login: str) -> bool:
        return self.custom_rule(self, properties, target_login)

    def _check_chief(self, properties: Properties, target_login: str) -> bool:
        return properties.get_is_chief(target_login)

    def check_owner(self, properties: Properties, target_login: str) -> bool:
        return properties.is_owner(target_login)

    def check_permission(self, properties: Properties, target_login: str, permission=None) -> bool:
        permission = permission or self.permission
        return permission in properties.permissions

    def is_available(self, properties: Properties, target_login: str) -> bool:
        if self.not_readonly and properties.readonly:
            return False
        if properties.get_target_type(target_login) not in self.show_on:
            return False
        if properties.is_superuser:
            return True
        if self.waffle:
            if waffle.switch_is_active(self.waffle):
                return False

        target_department_id = properties.get_target_data(target_login)['department_id']
        is_standard_access = (
            properties.is_observer_from_yamoney() or
            properties.is_observer_from_yandex() or
            'users.can_view_staff' in properties.permissions or
            (
                properties.observer.has_access_to_department_profiles(target_department_id) and
                properties.observer.user.has_perm('django_intranet_stuff.can_view_all_blocks')
            )
        )
        if is_standard_access:
            return (
                not self.checkers
                or any((check(properties, target_login) for check in self.checkers))
            )
        elif properties.is_observer_from_external():
            return self._external_access_check(properties, target_login, target_department_id)

    def _external_access_check(self, properties, target_login, target_department_id) -> bool:
        return (
            (self.rule_for_external and self.rule_for_external(self, properties, target_login)) or
            (self.external_owner and properties.is_owner(target_login)) or
            (self.external_other and properties.observer.has_access_to_department_profiles(target_department_id))
        )


class Block(BlockBase):
    """Правила показа блока"""


class LoadableBlock(BlockBase):
    """Правила показа подгружаемого блока"""


class Link(BlockBase):
    """Правила показа ссылки(кнопки)"""


class Pencil(BlockBase):
    """Правила показа карандашика для редактирования блока"""
    def __init__(self, *args, **kwargs):
        assert not kwargs.get('waffle'), 'Not hiding pencils for waffle'
        super(Pencil, self).__init__(*args, **kwargs)


class BaseRegistry(object):

    def __init__(self, properties: Properties):
        self.properties = properties

    def _get_items(self, cls, target_login):
        return {
            attr_name: True
            for attr_name, attr_instance in type(self).__dict__.items()
            if isinstance(attr_instance, cls)
            and self.is_available(attr=attr_name, target_login=target_login)
        }

    def get_blocks(self, target_login):
        return self._get_items(Block, target_login)

    @classmethod
    def get_waffle_switches(cls):
        switches = {
            getattr(attr_instance, 'waffle')
            for attr_name, attr_instance in cls.__dict__.items()
            if hasattr(attr_instance, 'waffle')
        }

        switches.discard('')
        return switches

    def get_loadable_blocks(self, target_login):
        return self._get_items(LoadableBlock, target_login)

    def get_links(self, target_login):
        return self._get_items(Link, target_login)

    def get_pencils(self, target_login):
        return self._get_items(Pencil, target_login)

    def is_available(self, attr, target_login):
        block_obj = getattr(self, attr)

        return block_obj.is_available(
            properties=self.properties,
            target_login=target_login,
        )
