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


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)

    def get_query_parameters(self, req):
        return self.convert_dict_to_bytes(req.args)

    def get_headers(self, req):
        return self.convert_dict_to_bytes(req.headers)

    def convert_dict_to_bytes(self, d):
        if d is not None:
            ret = str(len(d)).encode("utf-8") + b"\0"
            for key in d.keys():
                ret += key.encode("utf-8") + b"\0" + d[key].encode("utf-8") + b"\0"
            ret = ret[:-1]
            return ret
        return b""
