import re
import typing

from aws_lambda_tools.common.response import Response, ResponseMimeType
from conio_sdk.common import exceptions
from conio_sdk.logging.factory import LOGGING_FACTORY

_CAMEL_CASE_PATTERN = re.compile(r'(.)([A-Z])')


def _camel_to_upper_snake_case(s: str) -> str:
    return _CAMEL_CASE_PATTERN.sub(r'\1_\2', s).upper()[:-len('_EXCEPTION')]


def _error_with_status_code_and_exception_name(status_code: int):
    def _f(exc: exceptions.BFFException):
        return Response(
            {
                'error_code': _camel_to_upper_snake_case(type(exc).__name__),
                'arg': exc.args[0] if exc.args else None
            },
            content_type=ResponseMimeType.JSON,
            status_code=status_code)
    return _f


_bad_request_with_exception_name = _error_with_status_code_and_exception_name(400)
_forbidden_with_exception_name = _error_with_status_code_and_exception_name(403)
_conflict_with_exception_name = _error_with_status_code_and_exception_name(409)
_internal_error_with_exception_name = _error_with_status_code_and_exception_name(500)
_unavailable_with_exception_name = _error_with_status_code_and_exception_name(503)
_precondition_failed_with_exception_name = _error_with_status_code_and_exception_name(412)
_unprocessable_entity_with_exception_name = _error_with_status_code_and_exception_name(422)
_gone_with_exception_name = _error_with_status_code_and_exception_name(410)

_MAPPING_VALUE = typing.Union[Response, typing.Callable[[exceptions.BFFException], Response]]
_MAPPINGS = {
    exceptions.NotAuthenticatedException: Response(status_code=401),
    exceptions.InconsistentStateException: _conflict_with_exception_name,
    exceptions.InvalidTokenException: _bad_request_with_exception_name,
    exceptions.ExternalServiceException: _internal_error_with_exception_name,
    exceptions.BithustlerServiceCouldNotCreateSellerException: _internal_error_with_exception_name,
    exceptions.CardsServiceCouldNotCreatePayerException: _internal_error_with_exception_name,
    exceptions.CryptoProofException: Response(status_code=403),
    exceptions.BtcException: _bad_request_with_exception_name,
    exceptions.UnavailableBtcSubsystemException: _unavailable_with_exception_name,
    exceptions.AskAlreadyPaidException: _bad_request_with_exception_name,
    exceptions.BidAlreadyPaidException: _bad_request_with_exception_name,
    exceptions.CardsLimitsExceededException: _precondition_failed_with_exception_name,
    exceptions.DustAskException: _unprocessable_entity_with_exception_name,
    exceptions.FiatAmountTooLowException: _unprocessable_entity_with_exception_name,
    exceptions.InvalidPaymentMethodException: _bad_request_with_exception_name,
    exceptions.MultipleSellMethodsException: _precondition_failed_with_exception_name,
    exceptions.NoSuch3dSecureException: _unprocessable_entity_with_exception_name,
    exceptions.NoSuchSellMethodException: _precondition_failed_with_exception_name,
    exceptions.NoSuchSellerException: _precondition_failed_with_exception_name,
    exceptions.TradeExpiredException: _bad_request_with_exception_name,
    exceptions.TradingException: _bad_request_with_exception_name,
    exceptions.InvalidIbanException: _bad_request_with_exception_name,
    exceptions.DuplicateEmailAddressException: _conflict_with_exception_name,
    exceptions.InvalidTokenPayloadException: _forbidden_with_exception_name,
    exceptions.AcceptancesException: _precondition_failed_with_exception_name,
    exceptions.TooManyExplicitFeesExceptionForUserLevel: _precondition_failed_with_exception_name,
    exceptions.CouldNotFindExplicitFeesExceptionForUserLevel: _precondition_failed_with_exception_name,
    exceptions.BidExpiredException: _precondition_failed_with_exception_name,
    exceptions.BidIsInErrorException: _bad_request_with_exception_name,
    exceptions.SDKUpgradeRequestException: _gone_with_exception_name,
    exceptions.BidNotYetPaidException: _precondition_failed_with_exception_name

}  # type: typing.Dict[typing.Type[Exception], _MAPPING_VALUE]


def get_response_or_function_for_exception(exception_type: typing.Type[Exception]) \
        -> typing.Optional[typing.Union[Response, typing.Callable[[exceptions.BFFException], Response]]]:
    if exception_type in _MAPPINGS:
        return _MAPPINGS[exception_type]

    superclasses = exception_type.__bases__  # type: typing.Iterable[typing.Type[Exception]]
    for response_or_function in map(
            get_response_or_function_for_exception,
            filter(lambda d: bool(d), superclasses)):
        return response_or_function
    return None


def get_response_for_exception(exception: exceptions.BFFException) -> Response:
    LOGGING_FACTORY.root.exception('Error response: %s', exception)
    response_or_function = get_response_or_function_for_exception(type(exception))
    if not response_or_function:
        raise ValueError('No such mapping for {}'.format(type(exception)))
    return response_or_function if isinstance(response_or_function, Response) else response_or_function(exception)
