from pyramid.security import IAuthorizationPolicy
from zope.interface import implementer

from kinto.core import authorization as core_authorization

# Vocab really matters when you deal with permissions. Let's do a quick recap
# of the terms used here:
#
# Object URI:
#    An unique identifier for an object.
#    for instance, /buckets/blog/collections/articles/records/article1
#
# Object:
#    A common denomination of an object (e.g. "collection" or "record")
#
# Unbound permission:
#    A permission not bound to an object (e.g. "create")
#
# Bound permission:
#    A permission bound to an object (e.g. "collection:create")


# Dictionary which list all permissions a given permission enables.
PERMISSIONS_INHERITANCE_TREE = {
    '': {  # Granted via settings only.
        'bucket:create': {},
        'write': {},
        'read': {},
    },
    'bucket': {
        'write': {
            'bucket': ['write']
        },
        'read': {
            'bucket': ['write', 'read'],
        },
        'read:attributes': {
            'bucket': ['write', 'read', 'collection:create', 'group:create']
        },
        'group:create': {
            'bucket': ['write', 'group:create']
        },
        'collection:create': {
            'bucket': ['write', 'collection:create']
        },
    },
    'group': {
        'write': {
            'bucket': ['write'],
            'group': ['write']
        },
        'read': {
            'bucket': ['write', 'read'],
            'group': ['write', 'read']
        },
    },
    'collection': {
        'write': {
            'bucket': ['write'],
            'collection': ['write'],
        },
        'read': {
            'bucket': ['write', 'read'],
            'collection': ['write', 'read'],
        },
        'read:attributes': {
            'bucket': ['write', 'read'],
            'collection': ['write', 'read', 'record:create'],
        },
        'record:create': {
            'bucket': ['write'],
            'collection': ['write', 'record:create']
        },
    },
    'record': {
        'write': {
            'bucket': ['write'],
            'collection': ['write'],
            'record': ['write']
        },
        'read': {
            'bucket': ['write', 'read'],
            'collection': ['write', 'read'],
            'record': ['write', 'read']
        },
    }
}


def _resource_endpoint(object_uri):
    """Determine the resource name and whether it is the plural endpoint from
    the specified `object_uri`. Returns `(None, None)` for the root URL plural
    endpoint.
    """
    obj_parts = object_uri.split('/')
    plural_endpoint = len(obj_parts) % 2 == 0
    if plural_endpoint:
        # /buckets/bid/collections -> /buckets/bid
        obj_parts = obj_parts[:-1]

    if len(obj_parts) <= 2:
        # Root URL /buckets -> ('', False)
        return '', False

    # /buckets/bid -> buckets
    resource_name = obj_parts[-2]
    # buckets -> bucket
    resource_name = resource_name.rstrip('s')
    return resource_name, plural_endpoint


def _relative_object_uri(resource_name, object_uri):
    """Returns object_uri
    """
    obj_parts = object_uri.split('/')
    for length in range(len(obj_parts) + 1):
        parent_uri = '/'.join(obj_parts[:length])
        parent_resource_name, _ = _resource_endpoint(parent_uri)
        if resource_name == parent_resource_name:
            return parent_uri

    error_msg = "Cannot get URL of resource '{}' from parent '{}'."
    raise ValueError(error_msg.format(resource_name, object_uri))


def _inherited_permissions(object_uri, permission):
    """Build the list of all permissions that can grant access to the given
    object URI and permission.

    >>> _inherited_permissions('/buckets/blog/collections/article', 'read')
    [('/buckets/blog/collections/article', 'write'),
     ('/buckets/blog/collections/article', 'read'),
     ('/buckets/blog', 'write'),
     ('/buckets/blog', 'read')]

    """
    resource_name, plural = _resource_endpoint(object_uri)
    try:
        object_perms_tree = PERMISSIONS_INHERITANCE_TREE[resource_name]
    except KeyError:
        return []  # URL that are not resources have no inherited perms.

    # When requesting permissions for a single object, we check if they are any
    # specific inherited permissions for the attributes.
    attributes_permission = '{}:attributes'.format(permission) if not plural else permission
    inherited_perms = object_perms_tree.get(attributes_permission, object_perms_tree[permission])

    granters = set()
    for related_resource_name, implicit_permissions in inherited_perms.items():
        for permission in implicit_permissions:
            related_uri = _relative_object_uri(related_resource_name, object_uri)
            granters.add((related_uri, permission))

    # Sort by ascending URLs.
    return sorted(granters, key=lambda uri_perm: len(uri_perm[0]), reverse=True)


@implementer(IAuthorizationPolicy)
class AuthorizationPolicy(core_authorization.AuthorizationPolicy):
    def get_bound_permissions(self, *args, **kwargs):
        return _inherited_permissions(*args, **kwargs)


class RouteFactory(core_authorization.RouteFactory):
    pass
