#!/opt/python/run/lexi/venv/bin/python
import argparse
import logging
import logging.handlers
import os
import signal
import subprocess
import threading
import time
import json

from app.modules import data_store
from app.modules.eeg_python_utils import utils
from app.modules.speechmatics_client_docker import get_sesion_id_from_log, save_log, stop_docker_container, update_speechmatics_session_id

LEXI_BINARY_PATH = os.environ['LEXI_BINARY_PATH']
ICAP_COMPANY = os.environ['ICAP_COMPANY']
ENGINE = os.environ['ENGINE']
LOCAL_SERVER = os.environ['LOCAL_SERVER']
SPEECHMATICS_IP = os.environ['SPEECHMATICS_IP']

# set of process return codes that should be restarted
# sigfpe
# sigsegv
# sigabrt
RESTART_CODES = {-8, -11, -6}

# init logger
logger = logging.getLogger('LexiServer.wrapper')
handler = logging.handlers.RotatingFileHandler('/home/eeg/lexi_wrapper', maxBytes=5 * 1024 * 1024, backupCount=5)
formatter = logging.Formatter('%(asctime)s\t%(name)s\t%(levelname)s\t%(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)


def update_session_id(container_name, session_id):
    speechmatics_session_id = get_sesion_id_from_log(container_name)

    if speechmatics_session_id is not None:
        speechmatics_session_id = update_speechmatics_session_id(container_name, session_id, speechmatics_session_id)

    return speechmatics_session_id


def report_error(instance_id, error):
    instance = data_store.get_instance_settings(instance_id)
    instance['error'] = error
    data_store.set_instance_settings(instance_id, instance)


# Task for starting lexi
def create_lexi(instance_id, log_dir, log_prefix, status_path, p9000, container_id, session_id, instance_config):
    speechmatics_session_id = None

    logger.info('Message: Starting Up\tID: {0}\tConf: {1}\tLog Directory: {2}\tStatus Path: {3}'.format(
        instance_id, instance_config, log_dir, status_path))

    # icap_user = instance_config['icapuser']

    with open(os.devnull, 'w') as out:
        logger.info('log_dir: {}'.format(log_dir))
        process_args = [
            LEXI_BINARY_PATH,
            '-s',
            status_path,
            '--logdir',
            log_dir,
            '--logprefix',
            log_prefix,
            '--icapco',
            ICAP_COMPANY,
            '--engine',
            ENGINE,
            '--server',
            LOCAL_SERVER,
            '--sm_ip',
            SPEECHMATICS_IP + ':' + p9000
        ]
        logger.info('instance_config:\n{}'.format(utils.jformat(instance_config)))
        for option, value in instance_config.items():
            process_args.append('--{}'.format(option))
            process_args.append(str(value))

        logger.info('process_args:\n{}'.format(utils.jformat(process_args)))

        env = os.environ.copy()
        env['LD_LIBRARY_PATH'] = os.path.dirname(LEXI_BINARY_PATH)

        p = subprocess.Popen(process_args, stdout=out, stderr=subprocess.STDOUT, env=env)

        try:
            while True:
                time.sleep(3)

                # Check current session ID
                if speechmatics_session_id is None:
                    save_log(instance_id)
                    speechmatics_session_id = update_session_id(instance_id, session_id)

                # Check if the process is still running.
                returncode = p.poll()
                if returncode in RESTART_CODES:
                    logger.info('restart code: {}'.format(returncode))
                    # lexi segfaulted we should try to restart
                    logger.warning('Message: ts_lexi crashed with return code {0}, attempting restart\tID: {1}'.format(returncode, instance_id))
                    p = subprocess.Popen(process_args, stdout=out, stderr=subprocess.STDOUT)
                elif returncode:
                    logger.info('other error code: %d' % returncode)
                    # Non-restarting error that we need to surface.
                    report_error(instance_id, 'STOPPED with Returncode: {}'.format(returncode))
                    stop_docker_container(instance_id, container_id)

                    p = subprocess.Popen(process_args,stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, text = True)
                    outputMessage,errorMessage = p.communicate()

                    logger.info('Message: Crashed with return message, %s' % str(errorMessage))
                    return
                elif returncode == 0:
                    logger.info('clean exit')
                    stop_docker_container(instance_id, container_id)
                    return
        except KeyboardInterrupt:
            # If we get a SIGINT, that means it's time to terminate.

            # clean up nicely
            logger.info('Message: Shutting Down ID: {}'.format(instance_id))
            try:
                p.send_signal(signal.SIGINT)

                # Unfortunately, Python doesn't provide a way to wait for a
                # process to exit with a timeout. As a workaround, we set up
                # a timer that will send a SIGKILL to the process if it didn't
                # listen to the SIGINT.
                def kill_lexi_process():
                    try:
                        logger.warning(
                            'Message: Process didn\'t respond to SIGINT, sending SIGKILL\tID: {}'.format(instance_id))
                        p.kill()
                    except OSError:
                        # Process was already dead
                        logger.warning(
                            'Message: Attempted to kill process that was already stopped\tID: {}'.format(instance_id))

                kill_timer = threading.Timer(30, kill_lexi_process)
                kill_timer.start()
                p.wait()
                kill_timer.cancel()
            except OSError:
                # Process was already dead
                logger.warning(
                    'Message: Attempted to kill process that was already stopped\tID: {}'.format(instance_id))

            stop_docker_container(instance_id, container_id)


if __name__ == '__main__':
    # traceback.print_stack(limit=5)
    parser = argparse.ArgumentParser()
    parser.add_argument('instance_id')
    parser.add_argument('log_dir')
    parser.add_argument('log_prefix')
    parser.add_argument('status_path')
    parser.add_argument('p9000')
    parser.add_argument('container_id')
    parser.add_argument('session_id')
    parser.add_argument('instance_config')
    args = parser.parse_args()
    # print(args.instance_config)
    create_lexi(
        instance_id=args.instance_id,
        log_dir=args.log_dir,
        log_prefix=args.log_prefix,
        status_path=args.status_path,
        p9000=args.p9000,
        container_id=args.container_id,
        session_id=args.session_id,
        instance_config=json.loads(args.instance_config),
    )
