import logging
from typing import List, Optional

from httpclient import HttpRequest, HttpResponse
from messagebus import DefaultToken, HttpRequestMessage, MessageBus
from messagehandler import MessageHandlerBuilder

from ._CaseInsensitiveDict import CaseInsensitiveDict
from ._GetReplyMessageBus import GetReplyMessageBus
from ._InternalHttpException import InternalHttpException
from ._InvalidResponseMessage import InvalidResponseMessage

logger = logging.getLogger("HttpMessageHandler")


class HttpMessageHandler(object):
    def __init__(
        self,
        message_handler_builder,  # type: MessageHandlerBuilder
        cors_origin_hosts=None,  # type: Optional[List[str]]
        message_bus=None,  # type: MessageBus
        api_key_list=None,  # type: Optional[List[str]]
        api_key_header_field=None,  # type: Optional[str]
    ):
        # type: (...) -> None

        self.message_handler_builder = message_handler_builder
        self.cors_origin_hosts = cors_origin_hosts
        self.message_bus = message_bus
        self.api_key_list = set(api_key_list) if api_key_list is not None else None
        self.api_key_header_field = api_key_header_field or "x-api-key"

        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 _check_cors(self, request):
        # type: (HttpRequest) -> Optional[HttpResponse]

        if self.cors_origin_hosts is None:
            return None

        if 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)

        return None

    def _is_valid_api_key(self, request):
        # type: (HttpRequest) -> bool

        if self.api_key_list is None:
            return True

        request_api_key = request.headers.get(self.api_key_header_field)
        if request_api_key is None:
            return False

        if request_api_key not in self.api_key_list:
            return False

        return True

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

    def handle_request(self, request):
        # type: (HttpRequest) -> HttpResponse

        request.headers = CaseInsensitiveDict(request.headers)
        cors_resp = self._check_cors(request)
        if cors_resp is not None:
            return cors_resp

        if not self._is_valid_api_key(request):
            return HttpResponse(401, "Unauthorized", {})

        request_msg = HttpRequestMessage(
            url=request.url,
            method=request.method,
            body=request.body,
            query=request.query,
            http_headers=request.headers,
        )

        try:
            response_msg = self._handle_message(request_msg)
        except InternalHttpException as ex:
            logger.exception("Internal Http Error handling request")
            return HttpResponse(
                status_code=ex.status_code,
                body=ex.body,
                headers=ex.headers,
            )
        except Exception:
            logger.exception("Error handling request")
            return HttpResponse(
                500,
                "Internal Server Error".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(self.message_bus)
            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):
        body = message.body
        if not isinstance(body, bytes):
            body = message.body.encode("utf-8")

        http_response = HttpResponse(message.status_code, 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)

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