import logging
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
        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):
        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 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()

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