"""
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()
        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
        """
        from . import sshsessions
        for sshsess in sshsessions.values():
            if sshsess.authtok == authtok:
                return sshsess.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:
            res = Ssh.execute(sshsess, host=params['identity']['site']['host'], user=params['identity']['username'], cmd=params['interface']['statcmd'])
        except SshAgentException as e:
            logger.error(e)
            flask_restful.abort(400,message="{}".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"""
        params = get_conn_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'])
            flask_restful.abort(400, message=res['stderr'].decode())
        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))
            data = json.loads(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 'port' in connectparams and 'batchhost' in connectparams:
                tunnelport, pids = Ssh.tunnel(sshsess, port=connectparams['port'],
                                              batchhost=connectparams['batchhost'],
                                              user=username, host=loginhost,
                                              internalfirewall=firewall,
                                              localbind=appparams['localbind'])
                authtok = gen_authtok()
                sshsess.set_authtok(authtok)
                connectparams['localtunnelport'] = tunnelport
            connectparams['authtok'] = authtok
        return connectparams

    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')