import os
import lmdb
import logging
import functools
import contextlib

import synapse.exc as s_exc
import synapse.common as s_common
import synapse.reactor as s_react

import synapse.lib.lmdb as s_lmdb
import synapse.lib.tufo as s_tufo
import synapse.lib.cache as s_cache
import synapse.lib.const as s_const
import synapse.lib.scope as s_scope
import synapse.lib.config as s_config
import synapse.lib.msgpack as s_msgpack

logger = logging.getLogger(__name__)

authver = (0, 0, 1)

class Rules:
    '''
    Rules provides an abstraction for metadata
    based filtration of events and tufos.

    Each "rule" is a tuple of:
        (allow, perm): (bool, (str, dict))
    '''

    def __init__(self, rules):
        self._r_rules = rules
        self._r_match = s_cache.MatchCache()
        self._r_rules_by_perm = s_cache.Cache(onmiss=self._onRulesPermMiss)

    def _onRulesPermMiss(self, name):
        retn = []
        for rule in self._r_rules:
            if self._r_match.match(name, rule[1][0]):
                retn.append(rule)
        return retn

    def _cmprule(self, rule, perm):

        for prop, must in rule[1][1].items():

            valu = perm[1].get(prop)
            if valu is None:
                return False

            if not self._r_match.match(valu, must):
                return False

        return True

    def allow(self, perm):
        '''
        Returns True if the given perm/info is allowed by the rules.

        Args:
            perm ((str,dict)): The requested permission tuple

        Returns:
            (bool):  True if the rules allow the perm/info
        '''
        for rule in self._r_rules_by_perm.get(perm[0]):
            if self._cmprule(rule, perm):
                return rule[0]

        return False

def whoami():
    '''
    Return the name of the current synapse user for this thread.

    Example:

        name = s_auth.whoami()

    '''
    return s_scope.get('syn:user', 'root@localhost')

@contextlib.contextmanager
def runas(user):
    '''
    Construct and return a with-block object which runs as the given
    synapse user name.

    Example:

        import synapse.lib.auth as s_auth

        s_auth.runas('visi@localhost'):
            # calls from here down may use check user/perms
            dostuff()

    '''
    with s_scope.enter({'syn:user': user}):
        yield

def reqAdmin(f, attr='auth'):
    '''
    A Decorator to wrap a function to require it to be executed in a admin user context.

    Args:
        f: Function being wrapped.
        attr (str): Name of Auth local.

    Notes:
        This decorator should only be placed on methods on a class since it relies
        on having access to a local instance of a Auth object.

    Returns:
        Function results.

    Raises:
        s_exc.ReqConfOpt: If the auth local is not found on the object.
        s_exc.NoSuchUser: If the Auth local does not have a instance of the current user.
        s_exc.AuthDeny: If the user in scope is not a admin user.
    '''
    @functools.wraps(f)
    def _f(*args, **kwargs):
        auth = getattr(args[0], attr, None)  # type: s_auth.Auth
        if not auth:
            raise s_exc.ReqConfOpt(mesg='requires attr on local object',
                                   attr=attr)
        uname = whoami()
        uobj = auth.reqUser(uname)
        if not uobj.admin:
            raise s_exc.AuthDeny(mesg='Operation requires admin',
                                 name=f.__qualname__, user=uname)
        logger.info('Executing [%s][%s][%s] as [%s]',
                    f.__qualname__, args, kwargs, uname)
        return f(*args, **kwargs)

    return _f

class AuthMixin:
    def __init__(self, auth):
        '''
        A mixin that can be used to provide a helper for remote access to an
        Auth object.  The API endpoint ``authReact()`` can be used to manipulate
        the Auth object by only allowing admin users to perform actions.

        Args:
            auth (Auth): An auth instance. This is set to ``self.auth``.
        '''
        self.auth = auth
        self._mxrtor = s_react.Reactor()
        self._mxrtor.act('auth:get:users', self.__authGetUsers)
        self._mxrtor.act('auth:get:roles', self.__authGetRoles)
        # User actions
        self._mxrtor.act('auth:add:user', self.__authAddUser)
        self._mxrtor.act('auth:del:user', self.__authDelUser)
        self._mxrtor.act('auth:req:user', self.__authReqUser)
        self._mxrtor.act('auth:add:urole', self.__authAddUserRole)
        self._mxrtor.act('auth:add:urule', self.__authAddUserRule)
        self._mxrtor.act('auth:del:urole', self.__authDelUserRole)
        self._mxrtor.act('auth:del:urule', self.__authDelUserRule)
        # User admin actions
        self._mxrtor.act('auth:add:admin', self.__authAddAdmin)
        self._mxrtor.act('auth:del:admin', self.__authDelAdmin)
        # Role actions
        self._mxrtor.act('auth:req:role', self.__authReqRole)
        self._mxrtor.act('auth:add:role', self.__authAddRole)
        self._mxrtor.act('auth:del:role', self.__authDelRole)
        self._mxrtor.act('auth:add:rrule', self.__authAddRoleRule)
        self._mxrtor.act('auth:del:rrule', self.__authDelRoleRule)

    def authReact(self, mesg):
        '''
        General interface for interfacing with Auth via messages.

        Args:
            mesg ((str, dict)): A message we react to.

        Returns:
            (bool, ((str, dict))): isok, retn tuple.
        '''
        try:
            isok, retn = self._mxrtor.react(mesg)
        except Exception as e:
            logger.exception('Failed to process mesg [%s]', mesg)
            retn = s_common.getexcfo(e)
            isok = False
        finally:
            return isok, retn

    @reqAdmin
    def __authGetUsers(self, mesg):
        mname, mdict = mesg
        users = self.auth.getUsers()
        ret = (mname, {'users': users})
        return True, ret

    @reqAdmin
    def __authGetRoles(self, mesg):
        mname, mdict = mesg
        roles = self.auth.getRoles()
        ret = (mname, {'roles': roles})
        return True, ret

    @reqAdmin
    def __authReqUser(self, mesg):
        mname, mdict = mesg
        name = mdict.get('user')
        uobj = self.auth.reqUser(name)
        ret = s_tufo.tufo(mname, user=(name, uobj._getAuthData()))
        return True, ret

    @reqAdmin
    def __authReqRole(self, mesg):
        mname, mdict = mesg
        name = mdict.get('role')
        robj = self.auth.reqRole(name)
        ret = s_tufo.tufo(mname, role=(name, robj._getAuthData()))
        return True, ret

    @reqAdmin
    def __authAddUser(self, mesg):
        mname, mdict = mesg
        name = mdict.get('user')
        uobj = self.auth.addUser(name)
        ret = s_tufo.tufo(mname, user=(name, uobj._getAuthData()))
        return True, ret

    @reqAdmin
    def __authDelUser(self, mesg):
        mname, mdict = mesg
        name = mdict.get('user')
        _ret = self.auth.delUser(name)
        ret = s_tufo.tufo(mname, user=name, deleted=_ret)
        return True, ret

    @reqAdmin
    def __authDelRole(self, mesg):
        mname, mdict = mesg
        name = mdict.get('role')
        _ret = self.auth.delRole(name)