from messagebus import HttpResponseMessage, Message
from messagebusmock import MessageBusSpy
from testdoubleutil import SpyCall

from httpmessageprocessorutil.automapped import AutoMappedMessageProcessor, GlobalExceptionMap


class ExceptionWithoutInit(Exception):
    pass


class ExceptionWithInit(Exception):
    def __init__(self, a: int):
        self.a = a


class GlobalException(Exception):
    pass


class AnotherGlobalException(Exception):
    pass


class ParameterInteractor(object):
    def __init__(self):
        self.execute_call = SpyCall()

    def execute(self, obj):
        self.execute_call.register_call(obj)


msg_bus = MessageBusSpy()
msg = Message(0)


class TestFormatException(object):
    def test_when_no_extra_mapping_is_specified__500_is_returned(self):
        AutoMappedMessageProcessor(str, ParameterInteractor())\
            .format_exception(msg_bus, msg, None, None, ExceptionWithoutInit())

        assert isinstance(msg_bus.reply_message_call.args[1], HttpResponseMessage)
        assert msg_bus.reply_message_call.args[1].status_code == 500

    def test_when_extra_mapping_is_specified__the_specified_status_is_returned(self):
        status = 503
        AutoMappedMessageProcessor(str, ParameterInteractor(),
                                   {
                                       ExceptionWithoutInit: status
                                   }) \
            .format_exception(msg_bus, msg, None, None, ExceptionWithoutInit())

        assert isinstance(msg_bus.reply_message_call.args[1], HttpResponseMessage)
        assert msg_bus.reply_message_call.args[1].status_code == status

    def test_when_exception_does_not_have_init__the_body_is_empty(self):
        status = 503
        AutoMappedMessageProcessor(str, ParameterInteractor(),
                                   {
                                       ExceptionWithoutInit: status
                                   }) \
            .format_exception(msg_bus, msg, None, None, ExceptionWithoutInit())

        assert isinstance(msg_bus.reply_message_call.args[1], HttpResponseMessage)
        assert msg_bus.reply_message_call.args[1].body == b""

    def test_when_tuple_is_specified__status_and_body_are_set(self):
        status = 503
        body = "body"
        AutoMappedMessageProcessor(str, ParameterInteractor(),
                                   {
                                       ExceptionWithoutInit: (status, body)
                                   }) \
            .format_exception(msg_bus, msg, None, None, ExceptionWithoutInit())

        assert isinstance(msg_bus.reply_message_call.args[1], HttpResponseMessage)
        assert msg_bus.reply_message_call.args[1].status_code == status
        assert msg_bus.reply_message_call.args[1].body == body

    def test_when_exception_has_init__the_body_is_a_json(self):
        status = 503
        AutoMappedMessageProcessor(str, ParameterInteractor(),
                                   {
                                       ExceptionWithInit: status
                                   }) \
            .format_exception(msg_bus, msg, None, None, ExceptionWithInit(10))

        assert isinstance(msg_bus.reply_message_call.args[1], HttpResponseMessage)
        assert msg_bus.reply_message_call.args[1].body == b"""{"a": 10}"""


class TestGlobalExceptionMap(object):
    def _restore_global(self):
        GlobalExceptionMap.set({})

    def test_globalMap_isUsedWhenNoInstanceMapProvided(self):
        try:
            GlobalExceptionMap.set({GlobalException: 403})

            AutoMappedMessageProcessor(str, ParameterInteractor()) \
                .format_exception(msg_bus, msg, None, None, GlobalException())

            assert msg_bus.reply_message_call.args[1].status_code == 403
        finally:
            self._restore_global()

    def test_instanceMap_overridesGlobalMapForSameException(self):
        try:
            GlobalExceptionMap.set({GlobalException: 403})

            AutoMappedMessageProcessor(str, ParameterInteractor(),
                                       {GlobalException: 409}) \
                .format_exception(msg_bus, msg, None, None, GlobalException())

            assert msg_bus.reply_message_call.args[1].status_code == 409
        finally:
            self._restore_global()

    def test_globalAndInstanceMaps_areMerged(self):
        try:
            GlobalExceptionMap.set({GlobalException: 403})

            processor = AutoMappedMessageProcessor(
                str, ParameterInteractor(),
                {ExceptionWithoutInit: 422},
            )

            processor.format_exception(msg_bus, msg, None, None, GlobalException())
            assert msg_bus.reply_message_call.args[1].status_code == 403

            processor.format_exception(msg_bus, msg, None, None, ExceptionWithoutInit())
            assert msg_bus.reply_message_call.args[1].status_code == 422
        finally:
            self._restore_global()

    def test_globalMap_affectsExistingInstances(self):
        try:
            processor = AutoMappedMessageProcessor(str, ParameterInteractor())

            GlobalExceptionMap.set({GlobalException: 403})

            processor.format_exception(msg_bus, msg, None, None, GlobalException())
            assert msg_bus.reply_message_call.args[1].status_code == 403
        finally:
            self._restore_global()

    def test_globalMap_withTupleValue(self):
        try:
            GlobalExceptionMap.set({GlobalException: (403, "forbidden")})

            AutoMappedMessageProcessor(str, ParameterInteractor()) \
                .format_exception(msg_bus, msg, None, None, GlobalException())

            assert msg_bus.reply_message_call.args[1].status_code == 403
            assert msg_bus.reply_message_call.args[1].body == "forbidden"
        finally:
            self._restore_global()

    def test_globalMap_multipleExceptions(self):
        try:
            GlobalExceptionMap.set({
                GlobalException: 403,
                AnotherGlobalException: 401,
            })

            processor = AutoMappedMessageProcessor(str, ParameterInteractor())

            processor.format_exception(msg_bus, msg, None, None, GlobalException())
            assert msg_bus.reply_message_call.args[1].status_code == 403

            processor.format_exception(msg_bus, msg, None, None, AnotherGlobalException())
            assert msg_bus.reply_message_call.args[1].status_code == 401
        finally:
            self._restore_global()

    def test_setGlobalMapToNone_clearsIt(self):
        try:
            GlobalExceptionMap.set({GlobalException: 403})
            GlobalExceptionMap.set(None)

            AutoMappedMessageProcessor(str, ParameterInteractor()) \
                .format_exception(msg_bus, msg, None, None, GlobalException())

            assert msg_bus.reply_message_call.args[1].status_code == 500
        finally:
            self._restore_global()

