# encoding: utf-8
import json
import logging
import sys
from threading import Thread, Condition
from typing import List, Optional, Callable

from messagebus import Event, Message, DataType, TokenCreator
from messagehandler import MessageHandlerBuilder
from msgbus import (
    SE_SUCCESS,
    TK_CMP_TERM_NOW,
    TK_CMP_START,
    TK_EVT_EVENT,
    TK_SYS_ACK,
    TK_SYS_NAK,
    TK_HV_SERVICEONLINE,
    TK_HV_SERVICEOFFLINE,
    TK_CMP_RELOAD,
    TK_CMP_RECREATE,
)
from systools import sys_log_error

from ._MbContextMessageBus import MbContextMessageBus

logger = logging.getLogger(__name__)


class MbContextMessageHandler(object):
    def __init__(self, message_bus, service_name, service_type, required_services, event_handler_builder, recreate_listener=None):
        # type: (MbContextMessageBus, str, str, str, MessageHandlerBuilder, Callable[[Optional[str]], None]) -> None
        self.message_bus = message_bus
        self.service_name = service_name
        self.service_type = service_type
        self.required_services = required_services
        self.event_handler_builder = event_handler_builder
        self.reentrant_subjects = {}
        self.non_reentrant_subjects = {}
        self.thread_handling_subject = {}
        self.recreate_listener = recreate_listener

        self.queue_subjects = {}
        self.event_queue = []  # type: List[Event]
        self.event_queue_thread = None  # type: Thread
        self.event_queue_condition = Condition()  # type: Condition

        self.queue_tokens = {}
        self.message_queue = []  # type: List[Message]
        self.message_queue_thread = None  # type: Thread
        self.message_queue_condition = Condition()  # type: Condition

        self.group_to_filter = None  # type: Optional[str]
        self.finished = False

        if self.service_name and self.service_type != "":
            status = self.message_bus.mbcontext.MB_EasyWaitStart(
                "{}:{}".format(self.service_name, self.service_type),
                self.required_services
            )
            if status != SE_SUCCESS:
                sys_log_error("Terminating component after 'MB_EasyWaitStart'")
                sys.exit(status)

    def subscribe_reentrant_events(self, subjects):
        # type: (List[unicode]) -> MbContextMessageHandler
        for subject in subjects:
            self.reentrant_subjects[subject] = subject
            self.message_bus.subscribe(subject)

        return self

    def subscribe_non_reentrant_events(self, subjects):
        # type: (List[unicode]) -> MbContextMessageHandler
        for subject in subjects:
            self.non_reentrant_subjects[subject] = subject
            self.message_bus.subscribe(subject)

        return self

    def subscribe_queue_events(self, subjects):
        # type: (List[unicode]) -> MbContextMessageHandler

        subject_event_queue_thread = Thread(
            target=self._event_queue_method,
            name="Event Queue Thread",
        )
        subject_event_queue_thread.daemon = True
        subject_event_queue_thread.start()
        self.queue_subjects = subjects

        for subject in subjects:
            self.message_bus.subscribe(subject)

        return self

    def subscribe_queue_messages(self, tokens):
        # type: (List[int]) -> MbContextMessageHandler

        token_message_queue_thread = Thread(
            target=self._message_queue_method,
            name="Message Queue Thread",
        )
        token_message_queue_thread.daemon = True
        token_message_queue_thread.start()
        self.queue_tokens = tokens

        return self

    def handle_events(self):
        self.event_handler_builder.build_singletons()

        while not self.finished:
            msg = self.message_bus.mbcontext.MB_EasyGetMessage()

            # Check for component terminate request
            if (not msg) or (msg.token == TK_CMP_TERM_NOW):
                # Component terminated, warn the handler that we are terminated
                self.event_handler_builder.destroy_singletons()
                # reply the message
                self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
                # Finish the loop and terminate the component
                self.finished = True
                break

            if msg.token in (TK_CMP_RELOAD, TK_CMP_RECREATE):
                self.event_handler_builder.destroy_singletons()
                if msg.token == TK_CMP_RECREATE and self.recreate_listener is not None:
                    new_config = None
                    if msg.data is not None and len(msg.data) > 0:
                        new_config = msg.data.decode("utf-8")
                    self.recreate_listener(new_config)

                self.event_handler_builder.build_singletons()
                self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
                continue

            if msg.token in (TK_CMP_START, TK_HV_SERVICEONLINE, TK_HV_SERVICEOFFLINE):
                msg.token = TK_SYS_ACK
                self.message_bus.mbcontext.MB_EasyReplyMessage(msg)

            elif msg.token == TK_EVT_EVENT:
                params = msg.data.split(b'\x00')
                data = params[0] if len(params) > 0 else None
                subject = params[1] if len(params) > 1 else None
                subject = subject.decode("utf-8")
                evt_type = params[2] if len(params) > 2 else None
                if evt_type == b'':
                    evt_type = None
                if evt_type is not None:
                    evt_type = evt_type.decode("utf-8")
                sync = params[3] == b'true' if len(params) > 3 else False
                source_id = int(params[4]) if len(params) > 4 else None

                event = Event(
                    subject=subject,
                    event_type=evt_type,
                    data=data,
                    source=source_id,
                    synchronous=sync,
                    headers=self.parse_headers(msg.headers),
                    ttl=msg.ttl,
                )
                event.imp_message = msg

                if subject in self.non_reentrant_subjects:
                    handling_thread = False
                    if subject in self.thread_handling_subject:
                        handling_thread = self.thread_handling_subject[subject].is_alive()

                    # Se o evento é não reentrante e já temos uma thread tratando outra instância do mesmo evento,
                    # respondemos e não criamos outra thread
                    if handling_thread:
                        self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
                        continue

                if subject in self.queue_subjects:
                    # Se o evento é do tipo fila, enfileiramos a tupla e deixamos a thread responsável tratá-lo
                    with self.event_queue_condition:
                        self.event_queue.append(event)
                        self.event_queue_condition.notify()

                    self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
                    continue

                handler_thread = Thread(target=self._handle_event, args=(event,))
                handler_thread.daemon = True

                if subject in self.non_reentrant_subjects:
                    self.thread_handling_subject[subject] = handler_thread

                handler_thread.start()

                # Se o evento for syncrono, deixamos a thread responsável por dar o reply na mensagem
                if not sync:
                    self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
            else:
                token = msg.token
                if self.group_to_filter is not None and TokenCreator.is_in_group(token, self.group_to_filter):
                    msg.token = TK_SYS_NAK
                    self.message_bus.mbcontext.MB_EasyReplyMessage(msg)
                    continue

                data = msg.data
                data_format = msg.format
                if msg.data is None or msg.data == "":
                    data = None
                    data_format = DataType.empty

                message = Message(
                    token=token,
                    data_type=data_format,
                    data=data,
                    headers=self.parse_headers(msg.headers),
                    ttl=msg.ttl,
                )
                message.imp_message = msg

                if token in self.queue_tokens:
                    # Se a mensagem é do tipo fila, enfileiramos a tupla e deixamos a thread responsável tratá-lo
                    with self.message_queue_condition:
                        self.message_queue.append(message)
                        self.message_queue_condition.notify()

                    continue

                handler_thread = Thread(target=self._handle_message, args=(message,))
                handler_thread.daemon = True
                handler_thread.start()

    def _message_queue_method(self):
        my_condition = self.message_queue_condition
        my_queue = self.message_queue

        self._handle_each_queued_object(my_condition, my_queue, self._handle_message)

    def _event_queue_method(self):
        my_condition = self.event_queue_condition
        my_queue = self.event_queue

        self._handle_each_queued_object(my_condition, my_queue, self._handle_event)

    def _handle_each_queued_object(self, my_condition, my_queue, handler_fn):
        while not self.finished:
            while len(my_queue) > 0:
                with my_condition:
                    obj = my_queue.pop(0)

                try:
                    handler_fn(obj)
                except Exception: # noqa
                    pass

            with my_condition:
                my_condition.wait()

    def _handle_event(self, event):
        # type: (Event) -> None
        event_handler = None
        try:
            event_handler = self.event_handler_builder.build_message_handler(None, event)
            event_handler.handle_event(self.message_bus, event)
            if event.synchronous and not hasattr(event, '_replied'):
                logger.error("Sync event without reply: {}".format(event.subject))
        finally:
            if event_handler is not None:
                self.event_handler_builder.destroy_message_handler(event_handler)

    def _handle_message(self, message):
        # type: (Message) -> None
        message_handler = None
        try:
            message_handler = self.event_handler_builder.build_message_handler(message, None)
            message_handler.handle_message(self.message_bus, message)
        finally:
            if message_handler is not None:
                self.event_handler_builder.destroy_message_handler(message_handler)

    @staticmethod
    def parse_headers(
        headers,         # type: bytes
    ):
        if headers is None or headers == b"":
            return {}

        try:
            parts = headers.split(b"\0")

            header_qty = int(parts[0])
            if len(parts[1:]) * 2 == header_qty:
                headers = {}
                for i in range(header_qty):
                    headers[parts[1 + 2 * i].decode("utf-8")] = parts[2 + 2 * i].decode("utf-8")

                return headers
        except (Exception,):
            pass

        try:
            header_str = headers.decode("utf-8")
            return json.loads(header_str)
        except ValueError:
            return {}
