""" The access backend object base class """
from collections import defaultdict
from passlib.apps import custom_app_context as pwd_context
from pyramid.security import (Authenticated, Everyone,
                              effective_principals, Allow, Deny,
                              ALL_PERMISSIONS)
from pyramid.settings import aslist


def group_to_principal(group):
    """ Convert a group to its corresponding principal """
    if group in (Everyone, Authenticated) or group.startswith('group:'):
        return group
    elif group == 'everyone':
        return Everyone
    elif group == 'authenticated':
        return Authenticated
    else:
        return 'group:' + group


def groups_to_principals(groups):
    """ Convert a list of groups to a list of principals """
    return [group_to_principal(g) for g in groups]


class IAccessBackend(object):

    """ Base class for retrieving user and package permission data """

    mutable = False
    ROOT_ACL = [
        (Allow, Authenticated, 'login'),
        (Allow, 'admin', ALL_PERMISSIONS),
        (Deny, Everyone, ALL_PERMISSIONS),
    ]

    def __init__(self, request=None, default_read=None, default_write=None,
                 cache_update=None):
        self.request = request
        self.default_read = default_read
        self.default_write = default_write
        self.cache_update = cache_update

    @classmethod
    def configure(cls, settings):
        """ Configure the access backend with app settings """
        return {
            'default_read': aslist(settings.get('pypi.default_read',
                                                ['authenticated'])),
            'default_write': aslist(settings.get('pypi.default_write', [])),
            'cache_update': aslist(settings.get('pypi.cache_update',
                                                ['authenticated'])),
        }

    def allowed_permissions(self, package):
        """
        Get all allowed permissions for all principals on a package

        Returns
        -------
        perms : dict
            Mapping of principal to tuple of permissions

        """
        all_perms = {}
        for user, perms in self.user_permissions(package).iteritems():
            all_perms['user:' + user] = tuple(perms)

        for group, perms in self.group_permissions(package).iteritems():
            all_perms[group_to_principal(group)] = tuple(perms)

        # If there are no group or user specifications for the package, use the
        # default
        if not all_perms:
            for principal in groups_to_principals(self.default_read):
                all_perms[principal] = ('read',)
            for principal in groups_to_principals(self.default_write):
                if principal in all_perms:
                    all_perms[principal] += ('write',)
                else:
                    all_perms[principal] = ('write',)
        return all_perms

    def get_acl(self, package):
        """ Construct an ACL for a package """
        acl = []
        permissions = self.allowed_permissions(package)
        for principal, perms in permissions.iteritems():
            for perm in perms:
                acl.append((Allow, principal, perm))
        return acl

    def has_permission(self, package, perm):
        """ Check if this user has a permission for a package """
        current_userid = self.request.userid
        if current_userid is not None and self.is_admin(current_userid):
            return True

        perms = self.allowed_permissions(package)
        for principal in effective_principals(self.request):
            if perm in perms.get(principal, []):
                return True
        return False

    def user_principals(self, username):
        """
        Get a list of principals for a user

        Parameters
        ----------
        username : str

        Returns
        -------
        principals : list

        """
        principals = ['user:' + username, Everyone, Authenticated]
        if self.is_admin(username):
            principals.append('admin')
        for group in self.groups(username):
            principals.append('group:' + group)
        return principals

    def in_group(self, username, group):
        """
        Find out if a user is in a group

        Parameters
        ----------
        username : str
            Name of user. May be None for the anonymous user.
        group : str
            Name of the
 group. Supports 'everyo
ne', 'authenticated', and
            'admin'.

        Returns
        -------
        member : bool

        """
        if group in ('everyone', Everyone):
            return True
        elif username is None:
            return False
        elif group in ('authenticated', Authenticated):
            return True
        elif group == 'admin' and self.is_admin(username):
            return True
        else:
            return group in self.groups(username)

    def in_any_group(self, username, groups):
        """
        Find out if a user is in any of a set of groups

        Parameters
        ----------
        username : str
            Name of user. May be None for the anonymous user.
        groups : list
            list of group names. Supports 'everyone', 'authenticated', and
            'admin'.

        Returns
        -------
        member : bool

        """
        return any((self.in_group(username, group) for group in groups))

    def can_update_cache(self):
        """
        Return True if the user has permissions to update the pypi cache
        """
        return self.in_any_group(self.request.userid, self.cache_update)

    def need_admin(self):
        """
        Find out if there are any admin users

        This should only be overridden by mutable backends

        Returns
        -------
        need_admin : bool
            True if no admin user exists and the backend is mutable, False
            otherwise

        """
        return False

    def allow_register(self):
        """
        Check if the backend allows registration

        This should only be overridden by mutable backends

        Returns
        -------
        allow : bool

        """
        return False

    def verify_user(self, username, password):
        """
        Check the login credentials of a user

        For Mutable backends, pending users should fail to verify

        Parameters
        ----------
        username : str
        password : str

        Returns
        -------
        valid : bool
            True if user credentials are valid, false otherwise

        """
        stored_pw = self._get_password_hash(username)
        if self.mutable:
            # if a user is pending, user_data will be None
            user_data = self.user_data(username)
            if user_data is None:
                return False
        return bool(stored_pw and pwd_context.verify(password, stored_pw))

    def _get_password_hash(self, username):
        """ Get the stored password hash for a user """
        raise NotImplementedError

    def groups(self, username=None):
        """
        Get a list of all groups

        If a username is specified, get all groups that the user belongs to

        Parameters
        ----------
        username : str, optional

        Returns
        -------
        groups : list
            List of group names

        """
        raise NotImplementedError

    def group_members(self, group):
        """
        Get a list of users that belong to a group

        Parameters
        ----------
        group : str

        Returns
        -------
        users : list
            List of user names

        """
        raise NotImplementedError

    def is_admin(self, username):
        """
        Check if the user is an admin

        Parameters
        ----------
        username : str

        Returns
        -------
        is_admin : bool

        """
        raise NotImplementedError

    def group_permissions(self, package, group=None):
        """
        Get a mapping of all groups to their permissions on a package

        If a group is specified, just return the list of permis