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

from messagebus import JsonHttpResponseMessage
from messageprocessor import ApiRequest
from messageprocessorutil import UuidMessageProcessor
from messageprocessorutil.automapped._util import to_camel_case

from ._ApiRequestParser import ApiRequestParser
from ._ExceptionFormatter import ExceptionFormatter
from .exception import \
    InteractorWithoutExecuteMethod, \
    InteractorWithMoreThanOneParameterOnExecute, \
    ParameterSentToNoParameterInteractor


class AutoMappedMessageProcessor(UuidMessageProcessor):
    def __init__(self,
                 request_type,
                 interactor,
                 exception_map=None,
                 success_status=200,
                 extra_mapping=None,
                 event_name=None):
        # type: (Type, Any, Optional[Dict[type, Union[int, Tuple[int, str]]]], int, dict, Optional[str]) -> None  # noqa
        super(AutoMappedMessageProcessor, self).__init__(event_name)
        self.parser = ApiRequestParser(request_type, extra_mapping)
        self.success_status = success_status

        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):
        # type: (ApiRequest) -> 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 format_response(self, message_bus, message, event, input_obj, result):
        body = None
        if result is not None:
            body = self.convert_to_json(result)
        message_bus.reply_event(event, JsonHttpResponseMessage(self.success_status, body))

    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)

    def convert_to_json(self, obj):
        def mapper(o):
            if isinstance(o, datetime):
                return o.isoformat()
            if hasattr(o, "__dict__"):
                return self.convert_properties_to_camel_case(o)
            else:
                return str(o)

        return json.dumps(self.convert_properties_to_camel_case(obj), default=mapper)

    def convert_properties_to_camel_case(self, obj):
        if isinstance(obj, list):
            ret = []
            for o in obj:
                ret.append(self.convert_properties_to_camel_case(o))
            return ret
        else:
            d = obj.__dict__
            ret = {}
            for name in d:
                ret[to_camel_case(name)] = d[name]
            return ret
