# coding: utf-8


import re
import logging
from collections import defaultdict
from functools import wraps

from attr import attr, attributes
from idm.core.workflow.exceptions import ConflictValidationError
from django.utils.translation import ugettext_lazy as _

log = logging.getLogger(__name__)
WILDCARD = '%'


def translate(pattern):
    stack = []
    pattern = pattern.strip('/')
    for char in pattern:
        if char == WILDCARD:
            stack.append('.*')
        else:
            stack.append(re.escape(char))
    result = '/%s/' % ''.join(stack)
    return result


def pattern_matches(pattern, search_in):
    if WILDCARD in pattern:
        result = bool(re.search(translate(pattern), search_in, re.U))
    else:
        pattern = '/%s/' % pattern.strip('/')
        result = pattern in search_in
    return result


def listify(fn=None, container=list):
    """
    convert generator-function to return list-like container
    """
    def decorator(fn):
        @wraps(fn)
        def wrapper(*args, **kw):
            return container(fn(*args, **kw))

        return wrapper

    if fn is None:
        return decorator
    return decorator(fn)


@attributes(slots=True, frozen=True)
class Conflict(object):
    requested_path: str = attr()
    conflicting_system: 'System' = attr()
    conflicting_path: str = attr()
    email: str = attr()
    subj: 'Subject' = attr()
    node: 'RoleNode' = attr()

    @classmethod
    def from_rule(cls, rule: [str], subj: 'Subject', node: 'RoleNode') -> 'Conflict':
        from idm.core.models import System

        if len(rule) > 4 or len(rule) < 3:
            log.exception('Wrong conflict in workflow of the system %s', node.system.slug)
            raise ConflictValidationError(_('Неверная длина правила конфликта'))

        system_slug = None
        if len(rule) == 3:
            requested_path, conflicting_path, email = rule
        else:
            requested_path, system_slug, conflicting_path, email = rule

        system = node.system
        if system_slug is not None:
            try:
                system = System.objects.get(slug=system_slug)
            except System.DoesNotExist:
                log.exception('Wrong conflict in workflow of the system %s', node.system.slug)
                raise ConflictValidationError(_('Неверный slug системы в правиле конфликта'))

        return cls(
            requested_path=requested_path,
            conflicting_path=conflicting_path,
            email=email,
            conflicting_system=system,
            subj=subj,
            node=node
        )

    @listify
    def detect(self):
        from idm.core.models import Role
        node = self.node
        subj = self.subj
        if not pattern_matches(pattern=self.requested_path, search_in=node.value_path):
            return
        conflictable_roles = Role.objects.conflictable(subj, node, self.conflicting_system).select_related('system')
        for conflictable_role in conflictable_roles:
            if pattern_matches(pattern=self.conflicting_path, search_in=conflictable_role.node.value_path):
                subject_str = f'user {subj.user.pk}' if subj.is_user else f'group {subj.group.pk}'
                log.info(
                    'Conflict detected for subject %s node %s [conflicted with role %s for rule %s]',
                    subject_str, node.pk, conflictable_role.pk, self.conflicting_path)
                yield conflictable_role


def find_conflicts(subj: 'Subject', node: 'RoleNode', conflict_rules: [[str]]) -> dict:
    conflicts = defaultdict(set)
    for rule in conflict_rules:
        node.fetch_system()
        conflict = Conflict.from_rule(rule, subj, node)
        conflicting_roles = conflict.detect()
        if conflicting_roles:
            conflicts[conflict.email] |= {(role, conflict) for role in conflicting_roles}
    return conflicts
