import json

from logging import Logger
from typing import (
    Any,
    Optional,
    Union,
)

from helper import remove_accents
from posot import OrderTaker

from model.config import Config
from model.repository import PosRepositoryModel
from model.services import (
    CouponServiceModel,
    OrderServiceModel
)
from model.error_type import (
    NCouponErrorResponse,
    NotFoundErrorsType,
    PosValidationErrorsType,
)
from model.exception import (
    AuthValidationErrors,
    BlockedError,
    ClientCustomError,
    InUseError,
    NotFoundError,
    OperationErrors,
    ValidationError,
)
from model.response import Responses
from interactor.route.order.apply_coupon.dto import ApplyCouponRequest
from validation_util import check_if_pos_id_is_valid, validate_api_key, check_if_order_is_in_progress_or_totaled


FREE_COUPON_DATA = 'FREE_COUPON_DATA'


class ApplyCouponInteractor(object):
    def __init__(
            self,
            logger,             # type: Logger
            config,             # type: Config
            pos_repository,     # type: PosRepositoryModel
            coupon_service,     # type: CouponServiceModel
            order_service       # type: OrderServiceModel
    ):
        # type: (...) -> None

        self.logger = logger
        self.config = config
        self.pos_repository = pos_repository
        self.coupon_service = coupon_service
        self.order_service = order_service
        self.model = None
        self.pos_ot = None  # type: Optional[OrderTaker]
        self.price_list = None

    def execute(self, request):
        # type: (ApplyCouponRequest) -> None

        pos_id = validate_api_key(request, self.config.api_key_mapping)
        check_if_pos_id_is_valid(self.pos_repository, pos_id)

        coupon_data, coupon_applied = self.pos_repository.apply_coupon(
            pos_id=pos_id,
            coupon_code=request.coupon_code
        )

        self.model = self.pos_repository.get_model(pos_id)
        self.pos_ot = self.pos_repository.get_pos_ot(self.model)
        self.price_list = self.pos_repository.get_price_list(self.model)

        if self.pos_repository.has_current_order(self.model):
            check_if_order_is_in_progress_or_totaled(self.pos_repository, self.model)

        if coupon_applied:
            self.logger.info("Success coupon applied. CouponData: {}".format(coupon_data))
            order = self.pos_repository.get_current_order(self.model)
            order_id = order.get('orderId')
            order = self.pos_repository.get_order_details(
                pos_id=pos_id,
                order_id=order_id,
                pos_ot=self.pos_ot
            )
            if order:
                coupon_data = self.order_service.convert_mw_order_to_order_detail(order)

            return coupon_data

        else:
            self._raise_custom_error(
                coupon_data=coupon_data,
                language=request.language[:2] if request.language else "pt",
            )

    @staticmethod
    def _format_error_message(
        data,           # type: json
        language,       # type: str
        error_code,     # type: int
    ):
        # type: (...) -> Optional[str, dict]

        if data in (None, "None", ""):
            return

        try:
            data = json.loads(data)
            if error_code != 406:
                return data.get("message") or None

            message = data.get("message")
            translated_message = message[language] if message is not None and language in message.keys() else ""

            return remove_accents(text=translated_message)
        except Exception as _:
            return data

    @staticmethod
    def _get_error_code_message(
        message,    # type: str
    ):
        # type: (...) -> int

        if message == NCouponErrorResponse.COUPON_NOT_FOUND.value:
            return 404
        if message == NCouponErrorResponse.CUSTOM_ERROR.value:
            return 406
        if message == NCouponErrorResponse.COUPON_LOCKED.value:
            return 409
        if message == NCouponErrorResponse.COUPON_ALREADY_USED.value:
            return 410
        return 422

    def _raise_custom_error(
        self,
        coupon_data,    # type: Any[Union[str, json]]
        language,       # type: str
    ):
        # type: (...) -> None

        error_code = ""
        if '\0' in coupon_data:
            error_code, coupon_data = coupon_data.split('\0')
            status_code = self._get_error_code_message(message=error_code)
        else:
            status_code = self._get_error_code_message(message=coupon_data)

        message = self._format_error_message(
            data=coupon_data,
            language=language.lower(),
            error_code=status_code,
        )
        if not message:
            message = error_code

        self.logger.info("Error applying coupon. CouponData: {}".format(message))
        if status_code == Responses.AuthValidation:
            raise AuthValidationErrors(PosValidationErrorsType.CannotApplyCoupon)

        if status_code == Responses.NotFoundError:
            raise NotFoundError(
                NotFoundErrorsType.CouponNotFound,
                message,
            )
        if status_code == Responses.ClientCustomError:
            raise ClientCustomError(
                PosValidationErrorsType.CannotApplyCouponByPartnerRules,
                message,
            )
        if status_code == Responses.BlockedError:
            raise BlockedError(
                PosValidationErrorsType.CouponBlockedByAnotherRequest,
                message,
            )
        if status_code == Responses.InUseError:
            raise InUseError(
                PosValidationErrorsType.AlreadyUsedCoupon,
                message,
            )
        if status_code == Responses.OperationErrors:
            raise OperationErrors(PosValidationErrorsType.CannotApplyCoupon)

        raise ValidationError(
            PosValidationErrorsType.CannotApplyCoupon,
            message,
        )

