# -*- coding: utf-8 -*-

from collections import namedtuple

import sqlalchemy as sa

from .models.base import get_db_session

try:
    from pyramid.security import Allow, Deny, ALL_PERMISSIONS

except ImportError as e:
    Allow = 'Allow'
    Deny = 'Deny'


    # borrowed directly from pyramid - to avoid dependency on pyramid itself
    # source https://github.com/Pylons/pyramid/blob/master/pyramid/security.py

    class AllPermissionsList(object):

        """ Stand in 'permission list' to represent all permissions """

        def __iter__(self):
            return ()

        def __contains__(self, other):
            return True

        def __eq__(self, other):
            return isinstance(other, self.__class__)


    ALL_PERMISSIONS = AllPermissionsList()

__all__ = ['ANY_PERMISSION_CLS', 'ANY_PERMISSION',
           'resource_permissions_for_users',
           'permission_to_04_acls', 'permission_to_pyramid_acls']


class ANY_PERMISSION_CLS(object):
    def __eq__(self, other):
        return '__any_permission__' == other

    def __ne__(self, other):
        return '__any_permission__' != other


ANY_PERMISSION = ANY_PERMISSION_CLS()

PermissionTuple = namedtuple('PermissionTuple',
                             ['user', 'perm_name', 'type', 'group', 'resource',
                              'owner', 'allowed'])


def resource_permissions_for_users(models_proxy, perm_names, resource_ids=None,
                                   user_ids=None, group_ids=None,
                                   resource_types=None,
                                   limit_group_permissions=False,
                                   skip_user_perms=False,
                                   skip_group_perms=False,
                                   db_session=None):
    """
    Returns permission tuples that match one of passed permission names
    perm_names - list of permissions that can be matched
    user_ids - restrict to specific users
    group_ids - restrict to specific groups
    resource_ids - restrict to specific resources
    limit_group_permissions - should be used if we do not want to have
    user objects returned for group permissions, this might cause performance
    issues for big groups
    """
    db_session = get_db_session(db_session)

    # fetch groups and their permissions (possibly with users belonging
    # to group if needed)
    query = db_session.query(models_proxy.GroupResourcePermission.perm_name,
                             models_proxy.User,
                             models_proxy.Group,
                             sa.literal('group').label('type'),
                             models_proxy.Resource)

    query = query.join(models_proxy.Group,
                       models_proxy.Group.id ==
                       models_proxy.GroupResourcePermission.group_id)
    query = query.join(models_proxy.Resource,
                       models_proxy.Resource.resource_id ==
                       models_proxy.GroupResourcePermission.resource_id)
    if limit_group_permissions:
        query = query.outerjoin(models_proxy.User,
                                models_proxy.User.id == None)
    else:
        query = query.join(models_proxy.UserGroup,
                           models_proxy.UserGroup.group_id ==
                           models_proxy.GroupResourcePermission.group_id)

        query = query.outerjoin(models_proxy.User,
                                models_proxy.User.id ==
                                models_proxy.UserGroup.user_id)

    if resource_ids:
        query = query.filter(
            models_proxy.GroupResourcePermission.resource_id.in_(resource_ids))
    if resource_types:
        query = query.filter(
            models_proxy.Resource.resource_type.in_(resource_types))

    if (perm_names not in ([ANY_PERMISSION], ANY_PERMISSION) and perm_names):
        query = query.filter(
            models_proxy.GroupResourcePermission.perm_name.in_(perm_names))
    if group_ids:
        query = query.filter(
            models_proxy.GroupResourcePermission.group_id.in_(group_ids))

    if user_ids and not limit_group_permissions:
        query = query.filter(
            models_proxy.UserGroup.user_id.in_(user_ids))

    # 2nd query that will fetch users with direct resource permissions
    query2 = db_session.query(models_proxy.UserResourcePermission.perm_name,
                              models_proxy.User,
                              models_proxy.Group,
                              sa.literal('user').label('type'),
                              models_proxy.Resource)
    query2 = query2.join(models_proxy.User,
                         models_proxy.User.id ==
                         models_proxy.UserResourcePermission.user_id)
    query2 = query2.join(models_proxy.Resource,
                         models_proxy.Resource.resource_id ==
                         models_proxy.UserResourcePermission.resource_id)

    # group needs to be present to work for union, but never actually matched
    query2 = query2.outerjoin(models_proxy.Group,
                              models_proxy.Group.id == None)
    if (perm_names not in ([ANY_PERMISSION], ANY_PERMISSION) and perm_names):
        query2 = query2.filter(
            models_proxy.UserResourcePermission.perm_name.in_(perm_names))
    if resource_ids:
        query2 = query2.filter(
            models_proxy.UserResourcePermission.resource_id.in_(resource_ids))
    if resource_types:
        query2 = query2.filter(
            models_proxy.Resource.resource_type.in_(resource_types))
    if user_ids:
        query2 = query2.filter(
            models_proxy.UserResourcePermission.user_id.in_(user_ids))

    if not skip_group_perms and not skip_user_perms:
        query = query.union(query2)
    elif skip_group_perms:
        query = query2

    users = [PermissionTuple(row.User, row.perm_name, row.type,
                             row.Group or None, row.Resource, False, True)
             for row in query]
    return users


def permission_to_04_acls(permissions):
    """
    Legacy acl format kept for bw. compatibility
    :param permissions:
    :return:
    """
    acls = []
    for perm in permissions:
        if perm.type == 'user':
            acls.append((perm.user.id, perm.perm_name))
        elif perm.type == 'group':
            acls.append(('group:%s' % perm.group.id, perm.perm_name))
    return acls


def permission_to_pyramid_acls(permissions):
    """
    Returns a list of permissions in a format understood by pyramid
    :param permissions:
    :return:
    """
    acls = []
    for perm in permissions:
        if perm.type == 'user':
            acls.append((Allow, perm.user.id, perm.perm_name))
        elif perm.type == 'group':
            acls.append((Allow, 'group:%s' % perm.group.id, perm.perm_name))
    return acls
