import logging
import time
import signal
from threading import Event, Thread

from yandextank.stepper import StepperWrapper
from load.projects.bfg2020.src.guns import LogGun, SqlGun, CustomGun, HttpGun, ScenarioGun, UltimateGun
from load.projects.bfg2020.src.reader import BfgReader
from load.projects.bfg2020.src.worker import BFGMultiprocessing
from load.projects.bfg2020.src.stats import StatHTTPServer, RequestHandler


class FakeCore(object):
    def __init__(self, logger):
        self.log = logger

    def publish(self, publisher, key, value):
        self.log.debug('%s:%s\t%s', publisher, key, value)


class BFG(object):
    SECTION = 'bfg'

    """
    ammo_limit: -1
    ammo_type: caseline
    ammofile: ./real-big-one.ammo
    autocases: 0
    cache_dir: null
    cached_stpd: false
    chosen_cases: ''
    enabled: true
    enum_ammo: false
    file_cache: 8192
    force_stepping: 0
    gun_config:
      class_name: LoadTest
      init_param: ''
      module_name: luna
      module_path: ./
    gun_type: ultimate
    header_http: '1.0'
    headers: []
    instances: 20
    load_profile:
      load_type: rps
      schedule: line(1,20,10s)
    loop: -1
    package: yandextank.plugins.Bfg
    pip: '' here we need some analysis
    uris: []
    use_caching: true
    report_file: ''
    """

    def __init__(self, cfg):
        signal.signal(signal.SIGINT, self.exit_gracefully)
        signal.signal(signal.SIGTERM, self.exit_gracefully)

        self.cfg = cfg
        self.close_event = Event()
        self._bfg = None
        self.log = logging.getLogger("BFG")
        self.core = FakeCore(self.log)
        self.gun_type = None
        self.stepper_wrapper = StepperWrapper(self.core, cfg)
        self.log.info("Initialized BFG")
        self.report_filename = self.cfg.get("report_file", "phout.log")
        self.results_listener = None
        self.stat_server = None
        self.stat_server_thread = None
        self.stat_server_stoping = False

        self.gun_classes = {
            'log': LogGun,
            'sql': SqlGun,
            'custom': CustomGun,
            'http': HttpGun,
            'scenario': ScenarioGun,
            'ultimate': UltimateGun,
        }

    @staticmethod
    def get_key():
        return __file__

    def exit_gracefully(self, signum, frame):
        self.end_test(2)

    def get_available_options(self):
        return [
            "gun_type", "instances", "cached_stpd", "pip"
        ]

    def configure(self):
        self.log.info("Configuring BFG...")
        self.stepper_wrapper.read_config()
        self.stepper_wrapper.prepare_stepper()
        with open(self.report_filename, 'w'):
            pass

    def _write_results_into_file(self):
        """listens for messages on the q, writes to file. """
        reader = BfgReader(self.bfg.results, self.close_event)
        columns = ['receive_ts', 'tag', 'interval_real', 'connect_time', 'send_time', 'latency', 'receive_time',
                   'interval_event', 'size_out', 'size_in', 'net_code', 'proto_code']
        for entry in reader:
            if entry is not None:
                entry.receive_ts = entry.receive_ts.round(3)
                with open(self.report_filename, 'a') as report_file:
                    report_file.write(entry.to_csv(index=False, header=False, sep='\t', columns=columns))
            time.sleep(0.1)

    @property
    def bfg(self):
        if self._bfg is None:
            BFG = BFGMultiprocessing
            self._bfg = BFG(
                gun=self.gun,
                instances=self.stepper_wrapper.instances,
                stpd_filename=self.stepper_wrapper.stpd,
                cached_stpd=self.cfg.get("cached_stpd"),
            )
        return self._bfg

    def prepare_test(self):
        self.log.warning("Builded in Arcadia, pip deprecated")
        self.log.info("BFG using ammo type %s", self.cfg.get("ammo_type"))
        gun_type = self.cfg.get("gun_type")
        if gun_type in self.gun_classes:
            self.gun = self.gun_classes[gun_type](self.cfg.get("gun_config"))
        else:
            raise NotImplementedError(
                'No such gun type implemented: "%s"' % gun_type)

        self.results_listener = Thread(target=self._write_results_into_file, name="ResultsQueueListener")
        self.stat_server = StatHTTPServer(self.stepper_wrapper.steps, self.bfg.instance_counter, ('localhost', 1234), RequestHandler)
        self.stat_server_thread = Thread(target=self.stat_server.serve_forever, name="StatServer")
        self.log.info("Prepared BFG")

    def start_test(self):
        self.log.info("Starting BFG")
        self.start_time = time.time()
        self.bfg.start()
        if self.results_listener is not None:
            self.results_listener.start()
        else:
            self.log.fatal("Result listener is not initialized")
        if self.stat_server_thread is not None:
            self.stat_server_thread.start()
        else:
            self.log.fatal("StatServer is not initialized")

    def is_test_finished(self):
        if self.bfg.running():
            return -1
        else:
            self.log.info("BFG finished")
            self.close_event.set()
            self.shutdown_stat_server()
            return 0

    def shutdown_stat_server(self):
        if not self.stat_server_stoping:
            self.stat_server_stoping = True
            self.log.info("Statserver Terminating")
            self.stat_server.shutdown()
            self.stat_server.socket.close()
            self.stat_server_thread.join()
            self.log.info("Terminated")

    def end_test(self, retcode):
        if self.bfg.running():
            self.log.info("Terminating BFG")
            self.bfg.stop()
        self.close_event.set()
        return retcode
