import logging

from httpclient import HttpRequest, HttpResponse
from messagebus import HttpRequestMessage, DefaultToken
from messagehandler import MessageHandlerBuilder
from typing import Optional, Any, Dict, List

from ._GetReplyMessageBus import GetReplyMessageBus
from ._InvalidResponseMessage import InvalidResponseMessage

logger = logging.getLogger("HttpMessageHandler")


class HttpMessageHandler(object):
    def __init__(self, message_handler_builder, cors_origin_hosts=None):
        # type: (MessageHandlerBuilder, Optional[List[str]]) -> None
        self.message_handler_builder = message_handler_builder
        self.cors_origin_hosts = cors_origin_hosts
        if self.cors_origin_hosts is not None:
            self.cors_origin_hosts = {}
            for origin in cors_origin_hosts:
                self.cors_origin_hosts[origin] = True

    def build_singletons(self):
        self.message_handler_builder.build_singletons()

    def handle_request(self, request):
        # type: (HttpRequest) -> HttpResponse
        if self.cors_origin_hosts is not None and request.method == "OPTIONS" and "Origin" in request.headers:
            origin = request.headers["Origin"]
            requested_method = None
            if "Access-Control-Request-Method" in request.headers:
                requested_method = request.headers["Access-Control-Request-Method"]
            origin_host = self._get_origin_host(origin)

            headers = {}
            if (
                    (origin_host not in self.cors_origin_hosts or "*" not in self.cors_origin_hosts)
                    and requested_method is not None
            ):
                headers = {
                    "Access-Control-Allow-Origin": origin,
                    "Access-Control-Allow-Methods": "GET,POST,PUT,DELETE,PATCH"
                }
                if "Access-Control-Request-Headers" in request.headers:
                    headers["Access-Control-Allow-Headers"] = request.headers["Access-Control-Request-Headers"]

            return HttpResponse(200, b"", headers=headers)

        request_msg = HttpRequestMessage(request.url, request.method, request.body, request.query, request.headers)
        try:
            response_msg = self._handle_message(request_msg)
        except Exception as ex:
            logger.exception("Error handling request")
            return HttpResponse(500, "Internal Server Error: {}".format(str(ex)).encode("utf-8"), {})

        try:
            return self._parse_http_response_message(request, response_msg)
        except AttributeError:
            return self._parse_message(response_msg)

    def destroy_singletons(self):
        self.message_handler_builder.destroy_singletons()

    def _handle_message(self, msg):
        message_handler = None
        try:
            message_handler = self.message_handler_builder.build_message_handler(msg, None)
            msg_bus = GetReplyMessageBus()
            message_handler.handle_message(msg_bus, msg)
            return msg_bus.get_reply()
        finally:
            if message_handler is not None:
                self.message_handler_builder.destroy_message_handler(message_handler)

    def _parse_http_response_message(self, request, message):
        http_response = HttpResponse(message.status_code, message.body, message.http_headers)

        if "Access-Control-Allow-Origin" in message.http_headers:
            return http_response

        if self.cors_origin_hosts is None or "Origin" not in request.headers:
            return http_response

        origin = request.headers["Origin"]
        origin_host = self._get_origin_host(origin)
        if "*" not in self.cors_origin_hosts and origin_host not in self.cors_origin_hosts:
            return http_response
        http_response.headers["Access-Control-Allow-Origin"] = origin
        return http_response

    @staticmethod
    def _parse_message(message):
        if message.token != DefaultToken.TK_HTTP_MSG:
            raise InvalidResponseMessage("The response token must be TK_HTTP_MSG")

        try:
            parts = message.data.split(b"\0")
        except AttributeError:
            raise InvalidResponseMessage("Data is None. Must conform to the HttResponseMessagePattern")

        try:
            status_code = int(parts[0])
        except ValueError:
            raise InvalidResponseMessage("Status code could not be converted. "
                                         "Must conform to the HttResponseMessagePattern")
        except IndexError:
            raise InvalidResponseMessage("Data is empty. Must conform to the HttResponseMessagePattern")

        try:
            header_count = int(parts[1])
        except ValueError:
            raise InvalidResponseMessage("Header count could not be converted. "
                                         "Must conform to the HttResponseMessagePattern")
        except IndexError:
            raise InvalidResponseMessage("Header count could not be converted. "
                                         "Must conform to the HttResponseMessagePattern")

        headers = {}
        for i in range(header_count):
            try:
                header_key = parts[2 + 2 * i].decode("utf-8")
                header_value = parts[2 + 2 * i + 1].decode("utf-8")
                headers[header_key] = header_value
            except IndexError:
                raise InvalidResponseMessage("Not enough headers while getting position {}. Header count: {}. "
                                             "Must conform to the HttResponseMessagePattern".format(i, header_count))

        if len(parts) >= 2 + 2 * header_count + 1:
            body = b"\0".join(parts[2 + 2 * header_count:])
        else:
            body = b""

        return HttpResponse(status_code, body, headers)

    def _get_origin_host(self, origin):
        # type: (str) -> str
        index = origin.find("://")
        if index >= 0:
            return origin[index + 3:]
        return origin
