import logging
import threading
from abc import ABCMeta, abstractmethod
from typing import Optional, Dict

from messagebus import Message, Event, MessageBus
from messagehandler import MessageHandlerBuilder, MessageHandler

from contextmessagebus import ContextMessageBus, Context, MultiContextMessageBus

logger = logging.getLogger("ContextMessageBus.ContextMessageHandlerBuilder")


class ContextMessageHandlerBuilder(MessageHandlerBuilder):
    __metaclass__ = ABCMeta

    def __init__(self, message_bus):
        # type: (MessageBus) -> None
        self.message_bus = message_bus
        self.context_dict = None
        self.message_handler_context = {}

    def build_singletons(self):
        created_context = self.build_context()
        self.context_dict = {}
        for context_name in created_context.keys():
            context_message_bus = ContextMessageBus(context_name, self.message_bus)
            self.context_dict[context_name] = Context(context_name,
                                                      context_message_bus,
                                                      **created_context[context_name])
        self.build_singletons_with_context(self.context_dict)

    def build_message_handler(self, message, event):
        # type: (Optional[Message], Optional[Event]) -> MessageHandler
        try:
            context_name = self.get_context_name(message, event)
        except KeyError:
            logger.error("The 'context' was not present in the message or event: message: %s event: %s", message, event)
            context_name = None
        threading.current_thread()._edp_context_name = context_name
        if isinstance(self.message_bus, MultiContextMessageBus):
            self.message_bus.set_current_context(context_name)
        context = self.context_dict.get(context_name)
        if context is None:
            keys = list(self.context_dict.keys())
            logger.error("Could not retrieve context. Message: %s. Event: %s. Keys: %s", message, event, keys)

        extra_data = self.get_request_context_data(message, event)
        if context is not None and extra_data is not None:
            for key in extra_data:
                setattr(context, key, extra_data[key])
        message_handler = self.build_message_handler_with_context(context)
        self.message_handler_context[message_handler] = context_name
        return message_handler

    def destroy_message_handler(self, message_handler):
        if hasattr(threading.current_thread(), '_edp_context_name'):
            delattr(threading.current_thread(), '_edp_context_name')

        context_name = self.message_handler_context[message_handler]
        self.destroy_message_handler_with_context(self.context_dict[context_name], message_handler)
        del self.message_handler_context[message_handler]

    def destroy_singletons(self):
        self.destroy_singletons_with_context(self.context_dict)

    def get_request_context_data(self, message, event):
        # type: (Optional[Message], Optional[Event]) -> Dict[str, str]
        return None

    @abstractmethod
    def build_context(self):
        # type: () -> Dict[str, Context]
        raise NotImplementedError()

    @abstractmethod
    def build_singletons_with_context(self, context_dict):
        # type: (Dict[str, Context]) -> None
        raise NotImplementedError()

    @abstractmethod
    def build_message_handler_with_context(self, context):
        # type: (Context) -> MessageHandler
        raise NotImplementedError()

    @abstractmethod
    def destroy_message_handler_with_context(self, context, message_handler):
        # type: (Context, MessageHandler) -> None
        raise NotImplementedError()

    @abstractmethod
    def destroy_singletons_with_context(self, context_dict):
        # type: (Dict[str, Context]) -> None
        raise NotImplementedError()

    @staticmethod
    def get_context_name(message, event):
        if message is not None:
            return message.headers["context"]
        else:
            return event.headers["context"]
