import logging
import threading
from abc import (
    ABCMeta,
    abstractmethod,
)
from typing import (
    Dict,
    Generic,
    Optional,
    TypeVar,
)

from contextmessagebus import (
    Context,
    ContextMessageBus,
    MultiContextMessageBus,
)
from messagebus import (
    Event,
    Message,
    MessageBus,
)
from messagehandler import (
    MessageHandler,
    MessageHandlerBuilder,
)

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

T = TypeVar("T")

class ContextMessageHandlerBuilder(MessageHandlerBuilder, Generic[T], metaclass=ABCMeta):

    def __init__(self, message_bus: Optional[MessageBus] = None) -> None:
        self.message_bus = message_bus
        self.context_dict: Optional[Dict[str, T]] = 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,
                context_config=created_context[context_name],
            )
        self.build_singletons_with_context(self.context_dict)

    def build_message_handler(
        self,
        message: Optional[Message],
        event: 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)

        request_data = self.get_request_context_data(message, event)
        if context is not None and request_data is not None:
            context.request_data = request_data

        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.get(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: Optional[Message],
        event: Optional[Event],
    ) -> Optional[Dict[str, str]]:
        return None

    def add_context(
        self,
        context_name: str,
        context_config: T,
    ):
        context_message_bus = ContextMessageBus(context_name, self.message_bus)
        self.context_dict[context_name] = Context(
            context_name=context_name,
            message_bus=context_message_bus,
            context_config=context_config,
        )

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

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

    @abstractmethod
    def build_singletons_with_context(self, context_dict: Dict[str, Context[T]]):
        raise NotImplementedError()

    @abstractmethod
    def build_message_handler_with_context(self, context: Context[T]):
        raise NotImplementedError()

    @abstractmethod
    def destroy_message_handler_with_context(
        self,
        context: Optional[Context[T]],
        message_handler: MessageHandler,
    ) -> None:
        raise NotImplementedError()

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

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