"""
All the API endpoints for controling processes and tunnels via SSH
"""
import json
from flask import session, redirect, request, Response, make_response
from flask_restful import Resource
from flask import render_template
import flask_restful

from . import api, app
from .sshwrapper import Ssh, SshAgentException
from .tunnelstat import SSHSession


class AppParamsException(Exception):
    pass

class GetCert(Resource):
    """
    This class is necessary because I'm not reconfiguring
    SSHAuthZ to support CORS, but the TES does support CORS
    """

    def post(self):
        """
        takes a public key, returns to the SPA a certificate
        """
        print("in GetCert.post")
        data = request.get_json()
        print(data)
        return {'cert':GetCert.get_cert(data['token'], data['pubkey'], data['signing_url'])}

    @staticmethod
    def get_cert(access_token, pub_key, url):
        """
        Sign a pub key into a cert
        """
        import requests
        print("accss_token {}".format(access_token))
        print("pub_key {}".format(pub_key))
        print("url {}".format(url))
        sess = requests.Session()
        headers = {"Authorization":"Bearer %s"%access_token}
        data = {"public_key":pub_key}
        resp = sess.post(url, json=data, headers=headers, verify=False)
        print("get_cert returned from its external call")
        data = resp.json()
        import logging
        logger = logging.getLogger()
        logger.debug('attempted get_cert {}'.format(data))
        return data['certificate']

class TestAuth(Resource):
    """
    Tests whether the backend can login to the selected compute Resource
    """
    def get(self):
        """
        tell the SPA if the TES is logged in
        """
        return 'token' in session


class SSHAgent(Resource):
    def post(self):
        session.permanent = True
        from .tunnelstat import SSHSession
        sshsess = SSHSession.get_sshsession()
        data = request.get_json()
        import logging
        logger = logging.getLogger()
        logger.debug('adding a key to the session')
        sshsess.add_keycert(key=data['key'],cert=data['cert'])
        logger.debug('started an agent and added the key')
        sshsess.refresh()
        return "OK"

    def get(self):
        from .tunnelstat import SSHSession
        import logging
        logger = logging.getLogger()
        try:
            sshsess = SSHSession.get_sshsession()
            if sshsess.socket == None:
                return [] # The agent hasn't even been started yet
            return sshsess.get_cert_contents()
        except Exception as e:
            import traceback
            logger.debug('SSHAgent.get: Exception {}'.format(e))
            logger.debug(traceback.format_exc())
            flask_restful.abort(500)


    def delete(self):
        from .tunnelstat import SSHSession
        sshsess = SSHSession.get_sshsession()
        sshsess.kill()
        SSHSession.remove_sshsession()
        return []

def get_conn_params():
    """
    Return parameters relating to the backend compute service
    Retrieve them from the query string
    """
    identitystr = request.args.get('identity')
    identityparams = json.loads(identitystr)
    interfacestr = request.args.get('interface')
    interfaceparams = json.loads(interfacestr)
    try:
        appstr = request.args.get('app')
        appparams = json.loads(appstr)
    except:
        appparams = {}
    params = {}

    params['identity'] = identityparams
    params['interface'] = interfaceparams
    params['app'] = appparams

    params.update(interfaceparams)
    params['user'] = identityparams['username']
    params['host'] = identityparams['site']['host']

    return params


def get_app_params():
    """
    Return the parameters for the application retrieved from the Session
    """
    keys = ['startscript', 'paramscmd', 'client','localbind']
    appstr = request.args.get('app')
    returnvalue = json.loads(appstr)
    return returnvalue

class TunnelstatEP(Resource):
    """
    Endpoints used by the WS proxy
    """
    # def put(self, authtok):
    #     """
    #     update the last used time on a tunnel
    #     """
    #     Tunnelstat.update(authtok)

    def get(self, authtok):
        """
        given an authtoken, return the port of the tunnels
        """
        import logging
        logger = logging.getLogger()
        from . import sshsessions
        from flask import session

        for (id,sshsess) in sshsessions.items():
            for (tok,port) in sshsess.port.items():
                if tok == authtok:
                    print("found port {} for authtok {}".format(port,tok))
                    logger.debug("found port {} for authtok {}".format(port,tok))
                    return port
        return None

class JobStat(Resource):
    """
    endpoints to return info on jobs on the backend compute Resource
    """
    def get(self):
        """
        get info on the job from the backend
        """
        import logging
        logger = logging.getLogger()
        logger.info('/stat endpoint entered')
        params = get_conn_params()
        sshsess = SSHSession.get_sshsession()
        sshsess.refresh()
        logger.info('/stat endpoint, all parameters collected')
        try:
            host = params['identity']['site']['host']
        except (TypeError, KeyError) as e:
            flask_restful.abort(400, message="stat: definition of login host incomplete")
        try:
            user = params['identity']['username']
        except (TypeError, KeyError) as e:
            flask_restful.abort(400, message="stat: definition of username incomplete")
        try:
            cmd = params['interface']['statcmd']
        except (TypeError, KeyError) as e:
            flask_restful.abort(400, message="stat: definition of batch interface incomplete")
        try:
            res = Ssh.execute(sshsess, host=host, user=user, cmd=cmd)
        except SshAgentException as e:
            logger.error(e)
            flask_restful.abort(400, message="Identity error {}".format(e))
        if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
            logger.error(res['stderr'])
            flask_restful.abort(400, message=res['stderr'].decode())
        jobs = json.loads(res['stdout'].decode())

        # Attach the identity information to the job before returning it
        for j in jobs:
            j['identity'] = params['identity']

        return jobs

class JobCancel(Resource):
    """
    Terminate a job on the compute backend
    """
    def delete(self, jobid):
        """
        Terminate a job on the backend
        """
        params = get_conn_params()
        sshsess = SSHSession.get_sshsession()
        res = Ssh.execute(sshsess, host=params['identity']['site']['host'], user=params['identity']['username'],
                          cmd=params['interface']['cancelcmd'].format(jobid=jobid))
        if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
            flask_restful.abort(400, message=res['stderr'].decode())
        return res['stdout'].decode()


class JobSubmit(Resource):
    """
    Class dealing the starting a new job on the compute backend
    """
    def post(self):
        """starting a job is a post, since it changes the state of the backend"""
        import logging
        logger=logging.getLogger()
        params = get_conn_params()
        logger.debug('submitting with parameters {}'.format(params))
        sshsess = SSHSession.get_sshsession()
        res = Ssh.execute(sshsess, host=params['identity']['site']['host'], user=params['identity']['username'],
                          cmd=params['interface']['submitcmd'], stdin=params['app']['startscript'])
        if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
            print(res['stderr'])
            logger.debug(res['stderr'])
            flask_restful.abort(400, message=res['stderr'].decode())
        logger.debug(res['stdout'])
        return res['stdout'].decode()

def gen_authtok():
    """
    generate a random string suitable for an auth token stored in a cookie
    """
    import random
    import string
    return ''.join(random.SystemRandom().choice(string.ascii_uppercase +
                                                string.digits) for _ in range(16))

class JobConnect(Resource):
    """
    endpoints for connecting to an existing JobCancel
    """

    def create_tunnel(self, username, loginhost, appparams, batchhost, firewall, data):
        connectparams = {}
        sshsess = SSHSession.get_sshsession()

        if 'paramscmd' in appparams and appparams['paramscmd'] is not None:
            connectparams['batchhost'] = batchhost
            paramcmd = 'ssh -o StrictHostKeyChecking=no {batchhost} '.format(batchhost=batchhost) + appparams['paramscmd']
            res = Ssh.execute(sshsess, host=loginhost, user=username, cmd=paramcmd.format(data))
            try:
                data = json.loads(res['stdout'])
            except json.decoder.JSONDecodeError as e:
                raise AppParamsException(res['stderr']+res['stdout'])
            if len(res['stderr']) > 0:
                raise AppParamsException(res['stderr'])
            if 'error' in data:
                raise AppParamsException(data['error'])
            try:
                connectparams.update(json.loads(res['stdout']))
            except json.decoder.JSONDecodeError as e:
                print(res['stdout'])
                print(res['stderr'])
            if not (res['stderr'] == '' or res['stderr'] is None or res['stderr'] == b''):
                flask_restful.abort(400, message=res['stderr'].decode())

            if self.validate_connect_params(connectparams, username, loginhost):
                authtok = gen_authtok()
                tunnelport, pids = Ssh.tunnel(sshsess, port=connectparams['port'],
                                              batchhost=connectparams['batchhost'],
                                              user=username, host=loginhost,
                                              internalfirewall=firewall,
                                              localbind=appparams['localbind'], authtok=authtok)

                connectparams['localtunnelport'] = tunnelport
                connectparams['authtok'] = authtok
            else:
                raise AppParamsException("connection parameters invalid {} {} {}".format(connectparams,username,loginhost))
        return connectparams

    def validate_connect_params(self, connectparams, username, host):
        if not 'port' in connectparams:
            return False
        if not 'batchhost' in connectparams:
            return False
        try:
            intport = int(connectparams['port'])
        except Exception as e:
            return False
        if ' ' in username or '\n' in username: # This really needs more validation
            return False
        if ' ' in host or '\n' in host: # This really needs more validation
            return False
        return True

    def get(self, jobid, batchhost):
        """
        Connecting to a job is a get operation (i.e. it does not make modifications)
        """
        params = get_conn_params()
        print(params['interface'])
        appparams = get_app_params()
        data = request.get_json()
        try:
            connectparams = self.create_tunnel(params['identity']['username'],params['identity']['site']['host'],
                                            appparams, batchhost, params['interface']['internalfirewall'],
                                            data)
        except AppParamsException as e:
            return make_response(render_template('appparams.html.j2',data = "{}".format(e)))
        return self.connect(appparams, connectparams)

    def connect(self, appparams, connectparams):
        """
        perform the connection either by forking a local client or returning a redirect
        """
        import subprocess
        if 'cmd' in appparams['client'] and appparams['client']['cmd'] is not None:
            # We need for fork a local process such as vncviewer or a terminal
            # We may need a wrapper for local processes to find the correct
            # process on all OS
            cmdlist = []
            for cmdarg in appparams['client']['cmd']:
                cmdlist.append(cmdarg.format(**connectparams))
            app_process = subprocess.Popen(cmdlist, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
#            stdout, stderr = app_process.communicate()
#            if stderr is not "":
#                return "connected with cmd {} but got error {}".format(cmdlist,stderr)
        elif 'redir' in appparams['client'] and appparams['client']['redir'] is not None:
            template_response = Response()
            template_response.set_cookie(key='twsproxyauth', value=connectparams['authtok'])
            twsproxy = app.config['TWSPROXY']
            response = make_response(redirect(twsproxy+appparams['client']['redir'].
                                              format(**connectparams)))
            response.set_cookie('twsproxyauth', connectparams['authtok'])
            return response
        return "Connecting with cmd {}".format(cmdlist)


api.add_resource(TunnelstatEP, '/tunnelstat/<string:authtok>')
api.add_resource(GetCert, '/getcert')
api.add_resource(JobStat, '/stat')
api.add_resource(JobCancel, '/cancel/<int:jobid>')
api.add_resource(JobSubmit, '/submit')
api.add_resource(JobConnect, '/connect/<int:jobid>/<string:batchhost>')
# api.add_resource(SessionTest,'/sesstest')
#api.add_resource(StartAgent,'/startagent')
api.add_resource(SSHAgent,'/sshagent')