import inspect
import json
import logging
import os
import threading
from collections import defaultdict
from functools import partial

from kikyo import Kikyo, DataHub
from kikyo.settings import Settings

from kikyopp.annotation import KIKYOPP_SOURCE, KIKYOPP_SINK
from kikyopp.consumer.base import BaseConsumer
from kikyopp.consumer.kikyo import KikyoConsumer

log = logging.getLogger(__name__)


class BaseWorker:
    name: str
    kikyo: Kikyo
    consumer: BaseConsumer

    def __init__(self, settings: dict):
        self.settings = Settings(settings)

        log.info('settings: %s', json.dumps(self.settings, ensure_ascii=False, indent=4))

        self._init_env()
        assert self.name is not None
        self.debug = self.settings.get('debug', False)
        self.workers = self.settings.get('workers', 1)

        self._sink = None
        self._sink_method = None
        self._source = None
        self._source_method = None
        self._producer = None

        self._source = defaultdict(list)
        for name in dir(self):
            method = getattr(self, name)
            if inspect.ismethod(method):
                if hasattr(method, KIKYOPP_SOURCE):
                    self._source = getattr(method, KIKYOPP_SOURCE)
                    self._source_method = method
                if hasattr(method, KIKYOPP_SINK):
                    self._sink = getattr(method, KIKYOPP_SINK)
                    self._sink_method = method

        self.is_running = False

        self._init_components()
        if self._sink:
            self._producer = self.kikyo.component(cls=DataHub).create_producer(self._sink)

    def _init_env(self):
        worker_name = os.environ.get('KIKYOPP_WORKER_NAME')
        if worker_name:
            self.name = worker_name

    def _init_components(self):
        from kikyo import configure_by_consul

        self.kikyo = configure_by_consul(self.settings['kikyo_config_url'])
        self.consumer = KikyoConsumer(self)

    def start(self):
        if self.is_running:
            return

        if not self._source:
            log.warning('No defined source')

        self.is_running = True

        jobs = []
        for i in range(self.workers):
            t = threading.Thread(target=partial(self.consumer.run, self._source), daemon=True)
            t.start()
            jobs.append(t)

        for job in jobs:
            job.join()

    def process(self, data: dict):
        try:
            res = self._source_method(data)
            if res:
                if self._sink:
                    if not isinstance(res, list):
                        res = [res]
                    for r in res:
                        self._producer.send(r)
                        self._sink_method(r)
        except Exception as e:
            log.error(f'Error occurred: {e}', exc_info=True)

    def stop(self):
        if not self.is_running:
            return
        self.is_running = False
        self.consumer.stop()
