import json

from application.interactor.apply_coupon import ApplyCouponInteractor
from application.interactor.apply_coupon.dto.request import (
    ApplyCouponRequest,
    ApplyCouponContext,
)
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 ApplyCouponProcessor(UuidMessageProcessor):

    def __init__(
        self,        # type: ApplyCouponProcessor
        interactor,  # type: ApplyCouponInteractor
    ):
        # type: (...) -> None

        super(ApplyCouponProcessor, self).__init__()
        self.interactor = interactor

    def parse_data(
        self,        # type: ApplyCouponProcessor
        data,        # type: bytes
        event_type,  # type: str
    ):
        # type: (...) -> (int, int, str, int)

        parsed_data = json.loads(data)
        pos_id = int(parsed_data.get("posId"))
        originator_pos_id = int(parsed_data.get("originatorPosId") or pos_id)
        coupon_id = str(parsed_data["couponId"])
        user_auth_level = parsed_data.get("user_auth_level")
        if user_auth_level is None:
            user_auth_level = -1

        return ApplyCouponRequest(
            coupon_id=coupon_id,
            context=ApplyCouponContext(
                pos_id=pos_id,
                originator_pos_id=originator_pos_id,
                user_auth_level=user_auth_level
            )
        )

    def call_business(
            self,     # type: ApplyCouponProcessor
            request,  # type: ApplyCouponRequest
    ):
        # type: (...) -> None

        self.interactor.apply_coupon(apply_coupon_request=request)

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

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

    def format_exception(
        self,         # type: ApplyCouponProcessor
        message_bus,  # type: MessageBus
        message,      # type: Message
        event,        # type: None
        input_obj,    # type: ApplyCouponRequest
        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: ApplyCouponProcessor
        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)
