import logging
from typing import Dict, Callable, Optional

from messagebus import Message, Event

from ._LambdaMessageBus import LambdaMessageBus
from ._LambdaAdapter import LambdaAdapter
from ._LambdaEventType import LambdaEventType
from ._UnhandledEventException import UnhandledEventException
from messagehandler import MessageHandler, MessageHandlerBuilder


logger = logging.getLogger()


class LambdaMessageHandler(object):
    def __init__(self, message_bus, message_handler_builder, lambda_adapter):
        # type: (LambdaMessageBus, MessageHandlerBuilder, LambdaAdapter) -> None
        self.message_bus = message_bus
        self.message_handler_builder = message_handler_builder
        self.lambda_adapter = lambda_adapter

    def set_event_handler(self, event_handler_builder):
        self.message_handler_builder = event_handler_builder

    def handle_events(self, event, context):
        # type: (Dict, Dict) -> Dict
        try:
            lambda_event_type = self.lambda_adapter.get_type(event, context)
            if lambda_event_type == LambdaEventType.event or lambda_event_type == LambdaEventType.sync_event:
                message_bus_event = self.lambda_adapter.create_event(event, context)

                def handle_event_func(event_handler):
                    # type: (MessageHandler) -> None
                    event_handler.handle_event(self.message_bus, message_bus_event)

                self._execute_with_event_handler(handle_event_func, None, message_bus_event)
                if lambda_event_type == LambdaEventType.sync_event:
                    return self.lambda_adapter.create_reply(event, context, self.message_bus.get_reply())
            elif lambda_event_type == LambdaEventType.message:
                message = self.lambda_adapter.create_message(event, context)

                def handle_message_func(message_handler):
                    # type: (MessageHandler) -> None
                    message_handler.handle_message(self.message_bus, message)

                self._execute_with_event_handler(handle_message_func, message, None)

                return self.lambda_adapter.create_reply(event, context, self.message_bus.get_reply())
            else:
                raise Exception("Unhandled event type: {}".format(lambda_event_type))
        except UnhandledEventException:
            logger.exception("Unhandled event: {}".format(event))

    def _execute_with_event_handler(self, func, message, event):
        # type: (Callable[[MessageHandler], None], Optional[Message], Optional[Event]) -> None
        message_handler = None
        try:
            message_handler = self.message_handler_builder.build_message_handler(message, event)

            func(message_handler)
        finally:
            if message_handler is not None:
                self.message_handler_builder.destroy_message_handler(message_handler)
