from messagebus import (
    Message,
    MessageBus,
    Event,
)
from messagehandler import MessageHandlerBuilder
from typing import (
    Union,
)

from ._ExceptionProcessorCallback import ExceptionProcessorCallback
from ._ExpertMessageBusSpy import ExpertMessageBusSpy
from ._SendMessageMock import SendMessageMock
from ._SendMessageMockBuilder import SendMessageMockBuilder


class ApplicationMessageBus(MessageBus):
    def __init__(
        self,
        message_handler_builder: MessageHandlerBuilder,
        exception_callback: ExceptionProcessorCallback = None,
        message_bus: ExpertMessageBusSpy = None,
    ):
        self.message_handler_builder = message_handler_builder
        self.exception_callback = exception_callback or ExceptionProcessorCallback()
        self._accept_exceptions = False
        self._try_to_add_callback = exception_callback is None

        self.message_bus_spy = message_bus or ExpertMessageBusSpy()

        self.message_handler_builder.build_singletons()

    def publish_event(self, event: Event):
        return self._handle(event=event)

    def send_message(self, component, message):
        return self._handle(message=message)

    def subscribe(self, subscriptions):
        return

    def reply_message(self, message, reply_message):
        raise NotImplementedError()

    def reply_event(self, event, reply_message):
        raise NotImplementedError()

    def with_mock(
        self,
        mock: Union[SendMessageMockBuilder, SendMessageMock],
    ):
        self.message_bus_spy.add_send_message_response(mock=mock)

    @property
    def accept_exceptions(self):
        return self._accept_exceptions

    @accept_exceptions.setter
    def accept_exceptions(
        self,
        value: bool,
    ):
        self._accept_exceptions = value

    def restart(self):
        self.stop()
        self.start()

    def stop(self):
        self.message_handler_builder.destroy_singletons()

    def start(self):
        self.message_handler_builder.build_singletons()

    def _handle(self, message=None, event=None):
        event_handler = self.message_handler_builder.build_message_handler(
            message=message,
            event=event,
        )
        if self._try_to_add_callback:
            self._add_exception_callback(event_handler)

        if message is not None:
            event_handler.handle_message(
                message_bus=self.message_bus_spy,
                message=message
            )
        elif event is not None:
            event_handler.handle_event(
                message_bus=self.message_bus_spy,
                event=event
            )
        else:
            raise RuntimeError("event and message cannot be None")
        if not self._accept_exceptions and self.exception_callback.exception is not None:
            raise RuntimeError("exception while executing") from self.exception_callback.exception
        self.message_handler_builder.destroy_message_handler(event_handler)

        needs_reply = self._needs_reply(
            event=event,
            message=message,
        )
        if needs_reply and self.message_bus_spy.replied_message is None:
            raise RuntimeError("No reply received while processing")

        return self.message_bus_spy.replied_message

    def _add_exception_callback(
        self,
        event_handler
    ):

        if hasattr(event_handler, "executor_factory"):
            executor_factory = event_handler.executor_factory
            if hasattr(executor_factory, 'callbacks'):
                try:
                    executor_factory.callbacks.append(self.exception_callback)
                except AttributeError:
                    pass

    @staticmethod
    def _needs_reply(event: Event, message: Message) -> bool:
        if message is not None:
            return True

        if event is not None and event.synchronous:
            return True

        return False
