import sys
import signal
import importlib
import time
import traceback

from .workerutils import update_task, set_all_tasks_failed, requeue_all_tasks
from .client import get_task
from cputil import log
from cputil.log import bugsnag


def import_func(dottedpath):
    """
    For lazy importing of modules
    """
    components = dottedpath.split('.')
    modstring = '.'.join(components[:-1])
    mod = importlib.import_module(modstring)
    return getattr(mod, components[-1])


def run_task(task):
    update_task(task['_id'], 'in_progress')
    start = time.time()
    try:
        result = import_func(
            task['name'])(*task.get('args', []), **task.get('kwargs', {}))
        update_task(task['_id'], 'success', payload=result)
    except KeyboardInterrupt as e:
        log.error('GOT A KEYBOARD INTERRUPT')
        raise e
    except Exception as e:
        bugsnag.notify(e)
        update_task(task['_id'], 'failed',
                    payload=traceback.format_exception(*sys.exc_info()))
    # refetching task to get the correct status
    task = get_task(task['_id'])
    log.info('{} in {}s for {} - session({}) task({})'.format(
        task['status'], round(time.time() - start), task['name'],
        task['session_id'], task['_id']))


def unset_idle_session(sessid, sem, idle_sessions):
    # sessid will be in idle_sessions if this is not
    # the first task this worker runs
    if sessid in idle_sessions:
        sem.acquire()
        idle_sessions.remove(sessid)


def set_idle_session(sessid, sem, idle_sessions):
    idle_sessions.insert(0, sessid)
    sem.release()


def set_failed_session(sessid, sem, failed_sessions):
    failed_sessions.insert(0, sessid)
    sem.release()


def worker_init(sessid, modulepath, sem, failed_sessions):
    init_success = True
    try:
        module = importlib.import_module(modulepath)
        if not hasattr(module, 'init'):
            raise ValueError(
                'module {} must implement an init function'.format(module))

        getattr(module, 'init')(sessid)
    except Exception as e:
        # set all currently queued tasks for this session to failed because
        # you can't run
        bugsnag.notify(e)
        log.error(traceback.format_exc())
        log.error(('HUGE disaster, exception occured during the '
                   'init for {}. setting all tasks for this session to failed'
                   ).format(modulepath))
        # set_idle(sessid, sem, idle_sessions)
        set_failed_session(sessid, sem, failed_sessions)
        set_all_tasks_failed(
            sessid, traceback.format_exception(*sys.exc_info()))
        init_success = False
    return init_success


def worker(sessid, modulepath, q, idle_sessions, failed_sessions, sem):
    log.info('initializing session "{}"'.format(sessid))
    current_task = None

    def graceful_worker_exit(*_):
        log.info('exiting worker with session id {}'.format(sessid))
        if current_task is not None:
            log.info('re-qing task for {} {}'.format(
                current_task['name'], current_task['_id']))
            update_task(current_task['_id'], 'queued')
        requeue_all_tasks(sessid)
        sys.exit()

    signal.signal(signal.SIGINT, graceful_worker_exit)
    signal.signal(signal.SIGTERM, graceful_worker_exit)
    # intialize the module
    if worker_init(sessid, modulepath, sem, failed_sessions):
        while True:
            current_task = q.get()
            unset_idle_session(sessid, sem, idle_sessions)
            log.info('starting task {} - session({}) task({})'.format(
                current_task['name'], sessid, current_task['_id']))
            run_task(current_task)
            current_task = None
            if q.empty():
                set_idle_session(sessid, sem, idle_sessions)
