import random
import time
import datetime as dt

import threading
import queue

from mindsdb.utilities.config import Config
from mindsdb.utilities.log import initialize_log
from mindsdb.utilities import log
from mindsdb.interfaces.storage import db

from mindsdb.interfaces.jobs.jobs_controller import JobsExecutor


logger = log.get_log('jobs')


def execute_async(q_in, q_out):

    while True:
        task = q_in.get()

        if task['type'] != 'task':
            return

        record_id = task['record_id']
        history_id = task['history_id']

        executor = JobsExecutor()
        try:
            executor.execute_task_local(record_id, history_id)
        except (KeyboardInterrupt, SystemExit):
            q_out.put(True)
            raise

        except Exception:
            db.session.rollback()

        q_out.put(True)


class Scheduler:
    def __init__(self, config=None):
        self.config = config

        self.q_in = queue.Queue()
        self.q_out = queue.Queue()
        self.work_thread = threading.Thread(target=execute_async, args=(self.q_in, self.q_out))
        self.work_thread.start()

    def __del__(self):
        self.stop_thread()

    def stop_thread(self):
        self.q_in.put({
            'type': 'exit'
        })

    def scheduler_monitor(self):
        check_interval = self.config.get('jobs', {}).get('check_interval', 30)

        while True:

            logger.debug('Scheduler check timetable')
            try:
                self.check_timetable()
            except (SystemExit, KeyboardInterrupt):
                raise
            except Exception as e:
                logger.error(e)

            # different instances should start in not the same time

            time.sleep(check_interval + random.randint(1, 10))

    def check_timetable(self):
        executor = JobsExecutor()

        exec_method = self.config.get('jobs', {}).get('executor', 'local')

        for record in executor.get_next_tasks():
            logger.info(f'Job execute: {record.name}({record.id})')
            self.execute_task(record.id, exec_method)

        db.session.remove()

    def execute_task(self, record_id, exec_method):

        executor = JobsExecutor()
        if exec_method == 'local':
            history_id = executor.lock_record(record_id)
            if history_id is None:
                # db.session.remove()
                logger.info(f'Unable create history record for {record_id}, is locked?')
                return

            # run in thread

            self.q_in.put({
                'type': 'task',
                'record_id': record_id,
                'history_id': history_id,
            })

            while True:
                try:
                    self.q_out.get(timeout=3)
                    break
                except queue.Empty:
                    # update last date:
                    history_record = db.JobsHistory.query.get(history_id)
                    history_record.updated_at = dt.datetime.now()
                    db.session.commit()

        else:
            # TODO add microservice mode
            raise NotImplementedError()

    def start(self):

        config = Config()
        db.init()
        initialize_log(config, 'jobs', wrap_print=True)
        self.config = config

        logger.info('Scheduler starts')

        try:
            self.scheduler_monitor()
        except (KeyboardInterrupt, SystemExit):

            self.stop_thread()
            pass


def start(verbose=False):
    scheduler = Scheduler()

    scheduler.start()


if __name__ == '__main__':
    start()
