import json
from abc import abstractmethod
from typing import Any

from messagebus import (
    DataType,
    Message,
    MessageBus,
    AckMessage,
    InternalErrorMessage,
)
from messageprocessorutil import UuidMessageProcessor
from .dto import ApplyCouponErrorCode
from ..dto import ResponseToken
from ...model.customexception import ErrorApplyingBenefit
from ...model.repository.benefit_applier.exception import (
    CouponAlreadyUsed,
    CouponLocked,
    CouponCustomError,
    BenefitApplierNotFound,
)


class BaseApplyProcessor(UuidMessageProcessor):
    @abstractmethod
    def parse_data(
        self,        # type: BaseApplyProcessor
        data,        # type: bytes
        event_type,  # type: str
    ):
        # type: (...) -> None

        raise NotImplementedError()

    @abstractmethod
    def call_business(
            self,     # type: BaseApplyProcessor
            request,  # type: Any
    ):
        # type: (...) -> None

        raise NotImplementedError()

    def format_response(
        self,         # type: BaseApplyProcessor
        message_bus,  # type: MessageBus
        message,      # type: Message
        event,        # type: None
        request,      # type: Any
        response,     # type: None
    ):
        # type: (...) -> None

        message_bus.reply_message(
            message=message,
            reply_message=AckMessage()
        )

    def format_exception(
        self,         # type: BaseApplyProcessor
        message_bus,  # type: MessageBus
        message,      # type: Message
        event,        # type: None
        input_obj,    # type: Any
        exception,    # type: Exception
    ):
        # type: (...) -> None

        exception_map = {
            CouponAlreadyUsed: (ApplyCouponErrorCode.COUPON_ALREADY_USED, None),
            CouponLocked: (ApplyCouponErrorCode.COUPON_LOCKED, None),
            CouponCustomError: (ApplyCouponErrorCode.CUSTOM_ERROR, self._build_data_from_coupon_custom_error),
            BenefitApplierNotFound: (ApplyCouponErrorCode.COUPON_NOT_FOUND, None),
            ErrorApplyingBenefit: (ApplyCouponErrorCode.NDISCOUNT_ERROR, self._build_data_from_ndiscount_error)
        }
        error_code, data_builder = exception_map.get(type(exception), (None, None))

        if error_code is not None:
            data = error_code.name
            if data_builder is not None:
                data += "\0" + data_builder(exception)

            message_bus.reply_message(
                message=message,
                reply_message=Message(
                    token=ResponseToken.TK_NCOUPON_ERROR,
                    data_type=DataType.param,
                    data=data,
                )
            )
        else:
            message_bus.reply_message(
                message=message,
                reply_message=InternalErrorMessage(exception),
            )

    def format_parse_exception(
        self,         # type: BaseApplyProcessor
        message_bus,  # type: MessageBus
        message,      # type: Message
        event,        # type: None
        data,         # type: bytes
        exception,    # type: Exception
    ):
        # type: (...) -> None

        message_bus.reply_message(
            message=message,
            reply_message=InternalErrorMessage(exception),
        )

    @staticmethod
    def _build_data_from_coupon_custom_error(
        exception,  # type: CouponCustomError
    ):
        # type: (...) -> bytes

        return json.dumps({
            "message": {
                "pt": exception.message.pt,
                "en": exception.message.en,
                "es": exception.message.es,
            }
        }).encode("utf-8")

    @staticmethod
    def _build_data_from_ndiscount_error(
        exception,  # type: ErrorApplyingBenefit
    ):
        # type: (...) -> str

        return str(exception)
