import logging
from typing import Dict, Optional

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

from ._DefaultMessageProcessorExecutorFactory import DefaultMessageProcessorExecutorFactory
from ._MessageProcessor import MessageProcessor
from ._MessageProcessorExecutorFactory import MessageProcessorExecutorFactory

logger = logging.getLogger("MessageProcessor.MessageProcessorMessageHandler")


class MessageProcessorMessageHandler(MessageHandler):
    def __init__(self,
                 event_processors,
                 message_processors,
                 executor_factory=None,
                 subject_counter=None):
        # type: (Optional[Dict[str, MessageProcessor]], Optional[Dict[int, MessageProcessor]], Optional[MessageProcessorExecutorFactory], Optional[Dict[str, int]]) -> None  # noqa
        self.event_processors = event_processors
        self.message_processors = message_processors
        self.executor_factory = executor_factory \
            if executor_factory is not None \
            else DefaultMessageProcessorExecutorFactory([])
        self.subject_counter = subject_counter
        self.logger = logger

    def handle_event(self, message_bus, event):
        # type: (MessageBus, Event) -> None
        try:
            if self.subject_counter is not None:
                self._count_event(event.subject)

            if self.event_processors is not None:
                if self._has_event_type(event) and self._event_type_is_in_event_processors(event):
                    self._create_executor_and_execute(message_bus, event, self._get_event_type_key(event))
                    return

                if self._event_subject_is_in_event_processors(event):
                    self._create_executor_and_execute(message_bus, event, self._get_subject_key(event))
                    return

            raise MessageProcessorMessageHandler.EventNotMapped(event.subject, event.event_type)
        except:
            if self.logger is not None:
                self.logger.exception("Exception while handling event")
            raise

    def _count_event(self, subject):
        # type: (str) -> None
        subject_count = self.subject_counter.get(subject, 0)
        self.subject_counter[subject] = subject_count + 1

    def handle_message(self, message_bus, message):
        # type: (MessageBus, Message) -> None
        try:
            if self.message_processors is not None and message.token in self.message_processors:
                executor = self.executor_factory.build_executor(self.message_processors[message.token])
                executor.execute_processor(message_bus, message, None, message.data)
            else:
                message_bus.reply_message(message, NakMessage())
        except:
            if self.logger is not None:
                self.logger.exception("Exception handling message")
            raise

    def _has_event_type(self, event):
        return event.event_type is not None

    def _event_type_is_in_event_processors(self, event):
        return self._get_event_type_key(event) in self.event_processors

    def _get_event_type_key(self, event):
        return event.subject + "_" + event.event_type

    def _event_subject_is_in_event_processors(self, event):
        return self._get_subject_key(event) in self.event_processors

    def _get_subject_key(self, event):
        return event.subject

    def _create_executor_and_execute(self, message_bus, event, event_key):
        message_processor = self.event_processors[event_key]
        executor = self.executor_factory.build_executor(message_processor)
        executor.execute_processor(message_bus, None, event, event.data)

    class EventNotMapped(Exception):
        def __init__(self, subject, type):
            # type: (str, str) -> None
            self.subject = subject
            self.type = type

        def __str__(self):
            return "Event not mapped: {} - {}".format(self.subject, self.type)

        def __repr__(self):
            return "EventNotMapped('{}', '{}')".format(self.subject, self.type)
