import inspect
from typing import Type, Optional, Dict, Any, Union, Tuple

from messagebus import JsonHttpResponseMessage, HttpResponseMessage
from messageprocessorutil import UuidMessageProcessor

from httpmessageprocessor import ApiRequest
from httpmessageprocessorutil.sanitizer import Sanitizer, Nh3Sanitizer, SanitizeMode
from ._ApiRequestParser import ApiRequestParser
from ._ExceptionFormatter import ExceptionFormatter
from ._util import convert_to_json
from .exception import \
    InteractorWithoutExecuteMethod, \
    InteractorWithMoreThanOneParameterOnExecute, \
    ParameterSentToNoParameterInteractor

_UNSET = object()


class AutoMappedMessageProcessor(UuidMessageProcessor):
    _default_sanitizer = Nh3Sanitizer()

    @classmethod
    def set_default_sanitizer(cls, sanitizer):
        # type: (Optional[Sanitizer]) -> None
        cls._default_sanitizer = sanitizer

    def __init__(
        self,
        request_type,
        interactor,
        exception_map=None,
        success_status=200,
        extra_mapping=None,
        event_name=None,
        response_content_type=None,
        sanitizer=_UNSET,
        sanitize_mode=SanitizeMode.REQUEST_AND_RESPONSE,
    ):
        # type: (Optional[Type], Any, Optional[Dict[type, Union[int, Tuple[int, str], Callable]]], int, dict, Optional[str], Optional[str], Any, SanitizeMode) -> None  # noqa
        super(AutoMappedMessageProcessor, self).__init__(event_name)
        if sanitizer is _UNSET:
            sanitizer = self._default_sanitizer
        self.sanitizer = sanitizer
        self.sanitize_mode = sanitize_mode

        _sanitize_request = sanitize_mode in (
            SanitizeMode.REQUEST_AND_RESPONSE,
            SanitizeMode.REQUEST_ONLY,
        )
        _sanitize_response = sanitize_mode in (
            SanitizeMode.REQUEST_AND_RESPONSE,
            SanitizeMode.RESPONSE_ONLY,
        )
        self._request_sanitizer = sanitizer if _sanitize_request else None
        self._response_sanitizer = sanitizer if _sanitize_response else None

        self.parser = ApiRequestParser(request_type, extra_mapping, self._request_sanitizer)
        self.success_status = success_status
        self.response_content_type = response_content_type

        if not hasattr(interactor, "execute"):
            raise InteractorWithoutExecuteMethod()
        if len(inspect.signature(interactor.execute).parameters) > 1:
            raise InteractorWithMoreThanOneParameterOnExecute()
        self.interactor_has_parameter = False
        if len(inspect.signature(interactor.execute).parameters) == 1:
            self.interactor_has_parameter = True
        self.interactor = interactor

        self.exception_formatter = ExceptionFormatter(exception_map)
        self.exception_map = exception_map

    def parse_data(self, api_request, event_type):
        # type: (ApiRequest, Optional[str]) -> Any
        return self.parser.parse(api_request)

    def call_business(self, obj):
        if obj is not None:
            if not self.interactor_has_parameter:
                raise ParameterSentToNoParameterInteractor()

            return self.interactor.execute(obj)
        else:
            if self.interactor_has_parameter:
                return self.interactor.execute(None)
            else:
                return self.interactor.execute()

    def _sanitize_callback(self, value):
        if self._response_sanitizer is not None:
            return self._response_sanitizer.sanitize_response(value)
        return value

    def format_response(self, message_bus, message, event, input_obj, result):
        body = None

        if self.response_content_type is not None:
            headers = {
                "Content-Type": self.response_content_type
            }
            response = HttpResponseMessage(self.success_status, result, headers)
        else:
            if result is not None:
                body = convert_to_json(result, sanitize_method=self._sanitize_callback)
            response = JsonHttpResponseMessage(self.success_status, body)

        message_bus.reply_message(message, response)

    def format_exception(self, message_bus, message, event, input_obj, exception):
        self.exception_formatter.format(message_bus, message, exception, 500)

    def format_parse_exception(self, message_bus, message, event, data, exception):
        self.exception_formatter.format(message_bus, message, exception, 400)
