import threading
import time
from logging import Logger
from typing import Any

import zmq
from flask import Flask, request, Response
from messagebus import MessageBus, Event, DefaultToken, MessageBusException, MessageBusExceptionType
from zeromqmessagehandler import MessageDecoder


class SocketResource(object):
    def __init__(self, context, router_address):
        self.req_socket = context.socket(zmq.REQ)
        self.req_socket.connect(router_address)
        # on the first connect zeromq takes a few msecs to establish the internal pipes so we have to wait a while
        # before sending the first message
        time.sleep(0.100)

    def __del__(self):
        self.req_socket.close()


class FlaskApp(object):
    def __init__(self, message_bus, subject, request_timeout, logger):
        # type: (MessageBus, str, int, Logger) -> None
        self.message_bus = message_bus
        self.subject = subject
        self.request_timeout = request_timeout
        self.logger = logger

        self.app = None
        self.local_storage = threading.local()

    def run(self, name):
        # type: (str) -> Any
        self.app = Flask(name)
        self.app.add_url_rule("/", "catch_all",
                              self.handle_messages,
                              defaults={"path": "/"},
                              methods=["GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE"])
        self.app.add_url_rule("/<path:path>",
                              "catch_all",
                              self.handle_messages,
                              methods=["GET", "POST", "PUT", "OPTIONS", "HEAD", "DELETE"])

        return self.app

    def handle_messages(self, path):
        # type: (str) -> Response
        try:
            self.logger.info("Started processing request")

            if path != "/":
                path = "/" + path

            context_name = request.headers["Host"]
            http_data = b"\0".join([request.method.upper().encode("utf-8"),
                                   path.encode("utf-8"),
                                   self.get_headers(request),
                                   self.get_query_parameters(request),
                                   request.get_data()])

            try:
                self.logger.debug("Publishing event")
                reply = self.message_bus.publish_event(Event(self.subject,
                                                             data=http_data,
                                                             synchronous=True,
                                                             timeout=self.request_timeout,
                                                             headers={"context": context_name}))
                self.logger.debug("Received reply: {}".format(reply.token))
            except MessageBusException as ex:
                if ex.type == MessageBusExceptionType.timeout:
                    self.logger.error("Event timeout")
                    return Response(status=503)
                else:
                    self.logger.exception("Unknown exception while handling")
                    return Response(status=502)

            if reply.token == DefaultToken.TK_EVT_EVENT:
                self.logger.error("No component listening to our event")
                return Response(status=502)

            if reply.token != DefaultToken.TK_SYS_ACK:
                self.logger.error("Error on component listening to our event: {} - {}".format(reply.token, reply.data))
                return Response(status=500)

            parts = reply.data.split(b"\0")

            status_code = int(parts[0])
            header_count = int(parts[1])
            headers = {}
            for i in range(header_count):
                headers[parts[2 * i + 2].decode("utf-8")] = parts[2 * i + 3].decode("utf-8")
            body = b""
            if len(parts) > 2 * header_count + 2:
                body = b"\0".join(parts[2 * header_count + 2:])

            self.logger.info("Sending response")
            return Response(status=status_code, headers=headers, response=body)
        except:
            self.logger.exception("Error processing request")
            return Response(status=500)

    @staticmethod
    def get_query_parameters(req):
        return MessageDecoder.convert_dict_to_bytes(req.args)

    @staticmethod
    def get_headers(req):
        return MessageDecoder.convert_dict_to_bytes(req.headers)
