#

import itertools
import re

from rolepermissions.roles import AbstractUserRole, RolesClassRegister
from rolepermissions.utils import camelToSnake, snake_to_title

from customrolepermissions.states import FALSE_NI, State

_creation_counter = itertools.count()


class BaseRoleMetaClass(RolesClassRegister):
    def __new__(mcs, name, bases, attrs: dict):
        # make sure everything is correctly defined and not inherit directly
        attrs.setdefault('role_name', camelToSnake(name))
        attrs.setdefault('selectable_role', True)
        attrs.setdefault('verbose_name', snake_to_title(camelToSnake(name.replace('Role', ''))))
        attrs.setdefault('verbose_name_short', attrs['verbose_name'])
        attrs.setdefault('propagate_permissions', True)
        attrs.setdefault('grant_all_permissions', False)
        attrs.setdefault('false_all_permissions', False)
        attrs.setdefault('available_permissions', {})
        attrs.setdefault('order', next(_creation_counter) * 10 + 1000)  # put space for other roles to reorder themself

        new_cls = super().__new__(mcs, name, bases, attrs)

        for (permission_name, state) in new_cls.available_permissions.items():
            new_cls.available_permissions[permission_name] = State.normalize(state)

        # collect available_permissions from bases
        available_permissions = {}
        for base in reversed(bases):  # in MRO order
            if hasattr(base, 'available_permissions'):
                for (permission_name, state) in base.available_permissions.items():
                    if state is None:
                        available_permissions.pop(permission_name, None)
                    elif state.inheritable:
                        available_permissions[permission_name] = \
                            state.with_value(False) if new_cls.false_all_permissions and not state.grant else state

        # add extra permissions from new_cls to bases
        subclass_extra_available_permissions = dict(
            (permission_name, FALSE_NI)  # non inheritable False state
            for (permission_name, state) in new_cls.available_permissions.items()
            if (state is not None and state.propagatable and permission_name not in available_permissions)
        )
        if subclass_extra_available_permissions:
            _update_bases_available_permissions(new_cls, subclass_extra_available_permissions)  # recursive

        available_permissions.update(new_cls.available_permissions)

        # remove None permissions
        available_permissions = dict(  #
            (permission_name, state)
            for (permission_name, state) in available_permissions.items() if state is not None
        )

        # for permission_name in available_permissions:
        #     if not permission_name_re.match(permission_name):
        #         print(f'!!! {permission_name} is not standard')

        new_cls.available_permissions = available_permissions
        return new_cls


class BaseRole(AbstractUserRole, metaclass=BaseRoleMetaClass):
    selectable_role = False
    propagate_permissions = True
    grant_all_permissions = False
    false_all_permissions = False
    available_permissions = {}


def _update_bases_available_permissions(cls, subclass_extra_available_permissions):
    if not getattr(cls, 'propagate_permissions', True):
        return

    for base in cls.__bases__:
        if hasattr(base, 'available_permissions'):
            for (permission_name, state) in subclass_extra_available_permissions.items():
                if permission_name not in base.available_permissions:
                    if base.grant_all_permissions:
                        base.available_permissions[permission_name] = state.with_grant(True).with_inheritable(False)
                    else:
                        base.available_permissions[permission_name] = state

            _update_bases_available_permissions(base, subclass_extra_available_permissions)


def print_role_permissions(role):
    print(role.__name__, role.role_name, '    ', role.propagate_permissions)
    for (permission_name, state) in sorted(
        role.available_permissions.items(),
        key=lambda item: (not item[1].grant, not item[1].inheritable, not item[1].value, item[0])
    ):
        print('\t', permission_name, ':', state)
    print()


permission_name_re = re.compile(
    r"""
    ^
        (?P<section>[a-z]+(?:_[a-z0-9]+)*(?<!_read)(?<!_create)(?<!_update))
        (?:__
           (?P<action>create|read|update|delete|manage)
           (?:__
               (?P<relevance>all|rel|own)
           )?  # zero or one
        )?  # zero or one
    $
    """, re.X
)
