
import os
import json
import transport
import plugin_ix
import copy
import uuid
import pandas as pd
import io


class Manager :
    """
    {secure:{path,registry}}
    qcms secure <project>    will secure the project (download authentication modules)
    """
    def __init__(self,**_args):
        #
        # let's find the security configuration file
        #
        _appConfig = _args['config']
        self._config = None
        self._regPath = None
        self._authKey = None
        self._authContext = None
        _appContext = _args['config']['system']['context']
        self._loginURI = None

        _csv = """user,uri,allow\n*,*,1"""

        self._permissions = pd.read_csv(io.StringIO(_csv))
        _source = {} if 'source' not in _args['config']['system'] else _args['config']['system']['source']
        if 'secure' in _source :
            self._authContext = _appConfig['system']['source']['secure']['id']
            self._path      = _appConfig['system']['source']['secure']['path']
            self.inspect(self._path,'authentication configuration')
            
            if os.path.exists (self._path ) :
                _content = (open(self._path )).read()
                self._config = json.loads(_content)
                self._authKey= self._config['method']   #-- pam, oauth2, nextcloud, ssh
                #
                # the configuration {method,token}
        
           
            #
            # plugins home folder 
            _path = _appConfig['system']['source']['secure']['registry']
            self.inspect(_path,'plugins installed')
            self._plugins = plugin_ix.Registry(folder=_path)
            self._plugins.load()
            #
            # we need to load the permissions here ...
            #
            
            _kwargs = _appConfig['system']['source']['secure']
            
            self._loginURI  = [_appConfig['system'].get('context','')]
            if 'uri' in _kwargs :
                self._loginURI.appendd(_kwargs['uri'])
            else:
                self._loginURI.append('login')
            # if 'authorization' not in _kwargs:
            #
            # we will assume that all authenticated users have access to every part of the site
            #
            if 'authorization' in _kwargs and _kwargs['authorization']:
            #     #
            #     # loading permissions table from  a designated location
                reader = transport.get.reader(**_kwargs['authorization'])
                
                self._permissions = reader.read()
                
        
        #
        # @TODO: add the login page provided int the configuration with full permissions
        # or
        # self._permissions = pd.concat([pd.DataFrame([{"user":"*","uri":self._uri(),"allow":1}]),self._permissions])
    def inspect (self,path,what) :
        if not path or not os.path.exists(path) :
            raise Exception (f'Missing {what} {path}')
    def authenticate(self,**_args):
        _kwargs = copy.copy(_args)
        _kwargs['config'] = self._config
        # _user = self.login(**_kwargs)
        _request = _args['request']
        _method = self.module(f'{self._authKey}.login')
        
        _user = _method(_request,self._config)
        #
        # now we have to find a way to create a cookie that has the content of the
        
        _value = uuid.uuid4().hex
        
        if not _user :
            return {},None
        
        #
        # The additional parameter we can/should add the following
        #   max_age, secure, httpsonly, domain
        #
        if 'age' not in self._config :
            self._config['age'] = 3600
        return {self._authContext:json.dumps({'token':_value,'username':_user })},{"max_age":self._config['age']}
          
        
    def allow(self,**_args):
        """
        The analysis of permissions is based on propositional logic (permission table) to which the policy is applied i.e how we decide to allow access, we opt for the most restrictive approach
        """
        _request = _args['request']
        
        _stream = _request.cookies.get(self._authContext,None)
        
        _user = '*'
        _uri = _request.path if 'uri' not in _args else _args['uri']
        if _uri.endswith('/') :
            _uri = _uri[:-1]
        if _stream  :
            # _object = json.loads(_stream)            
            # _user = _object['user']
            return 1
        #
        # let's extract the matching criteria from the permissions data
        #   From the matching criteria we will select the most restrictive
        _query=f"""(user=='{_user}' or user=='*') and ((uri=='{_uri}' or uri=='*') or allow==1) """
        _values = self._permissions.query(_query).allow.tolist()
        if not _values:
            #
            #
            return False
        #
        # if of the matching criteria we do not have a denial, the system will allow access
        #
        return 0 not in _values

        #
        # we should also look into the cookie
        # return _uri not in self._config['permissions'] or self._config['permissions'] == '*' or self._config['uri'] == _uri
    def module (self,_name):
        """
        We need to identify the location of the plugins
        """
        _name = _name.strip()
        
        if self._plugins.has(_name):
            _m = self._plugins.get(_name)
            return copy.deepcopy(_m) if _m else None
        return None
    def method (self):
        
        return self._config['method'] if self._config else None
    # def _uri (self) :
    #     return self._config.get('uri',None)
    def loginURI (self):
        return '/'.join(self._loginURI) if self._loginURI else None

    
class parameters:
    """
    This is a decorator intended to abstractly handle a plugin
    """
    def __init__(self, **_args):
        self._input = _args['input']
        self._method = ['POST']
        # self._model = _args['model']
    
    def __call__(self, _callback):
        def _wrapper(*args, **kwargs):
            return _callback(*args, **kwargs)
        
        setattr(_wrapper, 'input', self._input)
        setattr(_wrapper, 'method', self._method)
        return _wrapper


