import logging
from typing import Dict, Callable
from flask import Flask, request

from messagebus import SwaggerMessage
from messagehandler import MessageHandler, MessageHandlerBuilder
from ._FlaskMessageBus import FlaskMessageBus
from ._FlaskEventType import FlaskEventType
from ._UnhandledEventException import UnhandledEventException

from ._FlaskAdapter import FlaskAdapter


logger = logging.getLogger()


class FlaskMessageHandler(object):
    def __init__(self, message_bus, event_handler_builder, flask_adapter):
        # type: (FlaskMessageBus, MessageHandlerBuilder, FlaskAdapter) -> None
        self.message_bus = message_bus
        self.event_handler_builder = event_handler_builder
        self.flask_adapter = flask_adapter

        self.app = None

    def set_event_handler_builder(self, event_handler_builder):
        self.event_handler_builder = event_handler_builder

    def run(self, name, host, port):
        # type: (str, str, int) -> None
        self.event_handler_builder.build_singletons()

        self.app = Flask(name)
        self.app.add_url_rule("/", "catch_all",
                              self.handle_events,
                              defaults={"path": "/"},
                              methods=["GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE"])
        self.app.add_url_rule("/<path:path>",
                              "catch_all",
                              self.handle_events,
                              methods=["GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE"])
        if host is not None:
            self.app.run(host=host, port=port)
            self.event_handler_builder.destroy_singletons()

    def handle_events(self, path):
        # type: (str) -> Dict
        try:
            if path != "/":
                path = "/" + path

            event_type = self.flask_adapter.get_type(path, request)
            if event_type == FlaskEventType.event or event_type == FlaskEventType.sync_event:
                message_bus_event = self.flask_adapter.create_event(path, request)

                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)
                if event_type == FlaskEventType.sync_event:
                    return self.flask_adapter.create_reply(path, request, self.message_bus.get_reply())
            elif event_type == FlaskEventType.message:
                message = self.flask_adapter.create_message(path, request)

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

                self._execute_with_event_handler(handle_message_func)

                return self.flask_adapter.create_reply(path, request, self.message_bus.get_reply())
            else:
                raise Exception("Unhandled event type: {}".format(event_type))
        except UnhandledEventException:
            logger.exception("Unhandled event")
            return self.flask_adapter.create_reply(
                path,
                request,
                SwaggerMessage(404, "No api found for: {} - {}".format(request.method, path)))

    def _execute_with_event_handler(self, func):
        # type: (Callable[[MessageHandler], None]) -> None
        event_handler = None
        try:
            event_handler = self.event_handler_builder.build_message_handler()

            func(event_handler)
        finally:
            if event_handler is not None:
                self.event_handler_builder.destroy_message_handler(event_handler)
