import logging
import threading
from abc import ABCMeta, abstractmethod
from typing import (
    Any,
    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():
            self.add_context(
                context_name=context_name,
                created_context=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]) -> Optional[Dict[str, str]]
        return None

    def add_context(self, context_name: str, created_context: Dict[str, Any]):
        context_message_bus = ContextMessageBus(context_name, self.message_bus)
        self.context_dict[context_name] = Context(
            context_name=context_name,
            message_bus=context_message_bus,
            **created_context
        )

    def remove_context(self, context_name: str):
        del self.context_dict[context_name]

    @abstractmethod
    def build_context(self):
        # type: () -> Dict[str, Any]
        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"]
