""" Access backend for storing permissions in using SQLAlchemy """
from sqlalchemy import (engine_from_config, Column, Text, Boolean, Table,
                        ForeignKey)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, backref
from sqlalchemy import orm
# pylint: disable=F0401,E0611
from zope.sqlalchemy import ZopeTransactionExtension
# pylint: enable=F0401,E0611

from .base import IMutableAccessBackend


# pylint: disable=C0103,W0231
Base = declarative_base()

association_table = Table(
    'pypicloud_user_groups', Base.metadata,
    Column('username', Text(), ForeignKey('pypicloud_users.username'), primary_key=True),
    Column('group', Text(), ForeignKey('pypicloud_groups.name'), primary_key=True)
)
# pylint: enable=C0103


class KeyVal(Base):

    """ Simple model for storing key-value pairs """
    __tablename__ = 'pypicloud_keyvals'
    key = Column(Text(), primary_key=True)
    value = Column(Text())

    def __init__(self, key, value):
        self.key = key
        self.value = value


class User(Base):

    """ User record """
    __tablename__ = 'pypicloud_users'
    username = Column(Text(), primary_key=True)
    password = Column('password', Text(), nullable=False)
    admin = Column(Boolean(), nullable=False)
    pending = Column(Boolean(), nullable=False)
    groups = orm.relationship('Group', secondary=association_table,
                              cascade='all', collection_class=set,
                              backref=backref('users', collection_class=set))

    def __init__(self, username, password, pending=True):
        self.username = username
        self.password = password
        self.groups = set()
        self.permissions = []
        self.admin = False
        self.pending = pending


class Group(Base):

    """ Group record """
    __tablename__ = 'pypicloud_groups'
    name = Column(Text(), primary_key=True)

    def __init__(self, name):
        self.name = name
        self.users = set()
        self.permissions = []


class Permission(Base):

    """ Base class for user and group permissions """
    __abstract__ = True
    package = Column(Text(), primary_key=True)
    read = Column(Boolean())
    write = Column(Boolean())

    def __init__(self, package, read, write):
        self.package = package
        self.read = read
        self.write = write

    @property
    def permissions(self):
        """ Construct permissions list """
        perms = []
        if self.read:
            perms.append('read')
        if self.write:
            perms.append('write')
        return perms

# pylint: disable=E1002


class UserPermission(Permission):

    """ Permissions for a user on a package """
    __tablename__ = 'pypicloud_user_permissions'
    username = Column(Text(), ForeignKey(User.username), primary_key=True)
    user = orm.relationship("User",
                            backref=backref('permissions',
                                            cascade='all, delete-orphan'))

    def __init__(self, package, username, read=False, write=False):
        super(UserPermission, self).__init__(package, read, write)
        self.username = username


class GroupPermission(Permission):

    """ Permissions for a group on a package """
    __tablename__ = 'pypicloud_group_permissions'
    groupname = Column(Text(), ForeignKey(Group.name), primary_key=True)
    group = orm.relationship("Group",
                             backref=backref('permissions',
                                             cascade='all, delete-orphan'))

    def __init__(self, package, groupname, read=False, write=False):
        super(GroupPermission, self).__init__(package, read, write)
        self.groupname = groupname


class SQLAccessBackend(IMutableAccessBackend):

    """
    This backend allows you to store all user and package permissions in a SQL
    database

    """

    def __init__(self, request=None, dbmaker=None, **kwargs):
        super(SQLAccessBackend, self).__init__(request, **kwargs)
        self.db = dbmaker()

        def cleanup(_):
            """ Close the session after the request """
            self.db.close()
        request.add_finished_callback(cleanup)

    @classmethod
    def configure(cls, settings):
        kwargs = super(SQLAccessBackend, cls).configure(settings)
        engine = engine_from_config(settings, prefix='auth.db.')
        kwargs['dbmaker'] = sessionmaker(
            bind=engine, extension=ZopeTransactionExtension())
        # Create SQL schema if not exists
        Base.metadata.create_all(bind=engine)
        return kwargs

    def allow_register(self):
        ret = self.db.query(KeyVal).filter_by(key='allow_register').first()
        return ret is None

    def set_allow_register(self, allow):
        if allow:
            self.db.query(KeyVal).filter_by(key='allow_register').delete()
        else:
            k = KeyVal('allow_register', 'false')
            self.db.add(k)

    def _get_password_hash(self, username):
        user = self.db.query(User).filter_by(username=username).first()
        if user:
            return user.password

    def groups(self, username=None):
        if username is None:
            query = self.db.query(Group)
            return [g.name for g in query]
        else:
            user = self.db.query(User).filter_by(username=username).first()
            if user is None:
                return []
            return [g.name for g in user.groups]

    def group_members(self, group):
        g = self.db.query(Group).filter_by(name=group).first()
        if not g:
            return []
        return [u.username for u in g.users]

    def is_admin(self, username):
        user = self.db.query(User).filter_by(username=username).first()
        return user and user.admin

    def group_permissions(self, package, group=None):
        if group is None:
            query = self.db.query(GroupPermission).filter_by(package=package)
            perms = {}
            for perm in query:
                perms[perm.groupname] = perm.permissions
            return perms
        else:
            perm = self.db.query(GroupPermission)\
                .filter_by(package=package, groupname=group).first()
            if perm:
                return perm.permissions
            return []

    def user_permissions(self, package, username=None):
        if username is None:
            query = self.db.query(UserPermission).filter_by(package=package)
            perms = {}
            for perm in query:
                perms[perm.username] = perm.permissions
            return perms
        else:
            perm = self.db.query(UserPermission)\
                .filter_by(package=package, username=username).first()
            if perm:
                return perm.permissions
            return []

    def user_package_permissions(self, username):
        query = self.db.query(UserPermission).filter_by(username=username)
        packages = []
        for perm in query:
            packages.append({
                'package': perm.package,
                'permissions': perm.permissions,
            })
        return packages

    def group_package_permissions(self, group):
        query = self.db.query(GroupPermission).filter_by(groupname=group)
        packages = []
        for perm in query:
            packages.append({
                'package': perm.package,
                'permissions': perm.permissions,
            })
        return packages

    def user_data(self, username=None):
        if username is None:
            query = self.db.query(User).filter_by(pending=False)
            users = []
            for user in query:
                users.append({
                    'username': user.username,
                    'admin': user.admin,
                })
            return users
        else:
            user = self.db.query(User).filter_by(username=username,
                                                 pending=False).first()
            if user is not None:
                return {
                    'username': user.username,
                    'admin': user.admin,
                    'groups': [g.name for g in user.groups],
                }

    def need_admin(self):
        return self.db.query(User).filter_by(admin=True).first() is None

    def _register(self, username, password):
        user = User(username, password)
        self.db.add(user)

    def pending_users(self):
        query = self.db.query(User).filter_by(pending=True)
        return [u.username for u in query]

    def approve_user(self, username):
        user = self.db.query(User).filter_by(username=username).first()
        if user is not None:
            user.pending = False

    def _set_password_hash(self, username, password_hash):
        user = self.db.query(User).filter_by(username=username).first()
        if user is not None:
            user.password = password_hash

    def delete_user(self, username):
        self.db.query(User).filter_by(username=username).delete()
        clause = association_table.c.username == username
        self.db.execute(association_table.delete(clause))

    def set_user_admin(self, username, admin):
        user = self.db.query(User).filter_by(username=username).first()
        if user is not None:
            user.admin = admin

    def edit_user_group(self, username, groupname, add):
        user = self.db.query(User).filter_by(username=username).first()
        group = self.db.query(Group).filter_by(name=groupname).first()
        if user is not None and group is not None:
            if add:
                user.groups.add(group)
            else:
                user.groups.remove(group)

    def create_group(self, group):
        self.db.add(Group(group))

    def delete_group(self, group):
        self.db.query(Group).filter_by(name=group).delete()
        clause = association_table.c.group == group
        self.db.execute(association_table.delete(clause))

    def edit_user_permission(self, package, username, perm, add):
        record = self.db.query(UserPermission)\
            .filter_by(package=package, username=username).first()
        if record is None:
            if not add:
                return
            record = UserPermission(package, username)
            self.db.add(record)
        if perm == 'read':
            record.read = add
        elif perm == 'write':
            record.write = add
        else:
            raise ValueError("Unrecognized permission '%s'" % perm)
        if not record.read and not record.write:
            self.db.delete(record)

    def edit_group_permission(self, package, group, perm, add):
        record = self.db.query(GroupPermission)\
            .filter_by(package=package, groupname=group).first()
        if record is None:
            if not add:
                return
            record = GroupPermission(package, group)
            self.db.add(record)
        if perm == 'read':
            record.read = add
        elif perm == 'write':
            record.write = add
        else:
            raise ValueError("Unrecognized permission '%s'" % perm)
        if not record.read and not record.write:
            self.db.delete(record)
