import json
import logging

from xml.etree import cElementTree as eTree

import sysactions

from actions.util import (
    get_authorization,
    get_user_level,
)
from model.customexception import (
    ErrorApplyingDiscount,
    DiscountMinLevelException,
    UnexpectedResponse,
)
from typing import Optional, Dict

from actions.sale_line.set_custom_properties import set_custom_properties
from bustoken import (
    TK_NCOUPON_APPLY_COUPON,
    TK_NCOUPON_APPLY_BENEFIT,
    create_token,
    MSGPRT_LOW,
)
from msgbus import (
    FM_PARAM,
    TK_SYS_ACK,
    MBTimeout,
    MBException,
)
from mw_helper import (
    show_keyboard,
)

from sysactions import action, get_model, get_current_order, show_messagebox, get_posot, translate_message

from actions import mb_context
from custom_sysactions import user_authorized_to_execute_action

from ._ApplyCouponErrorCode import ApplyCouponErrorCode
from ._CustomError import CustomError
from ._TranslatedMessage import TranslatedMessage

NCOUPON_MESSAGE_GROUP = "113"
TK_NCOUPON_ERROR = create_token(
    priority=MSGPRT_LOW,
    message_grourp=NCOUPON_MESSAGE_GROUP,
    number="F001",
)

logger = logging.getLogger("PosActions")


@action
@user_authorized_to_execute_action
def do_apply_coupons(pos_id, table_id=None, seat=''):
    coupon_code = show_keyboard(
        posid=pos_id,
        message="$ENTER_COUPON_CODE",
        defvalue="",
        title="",
        numpad=False,
        buttons='$OK|$PREVIOUS',
    )
    if coupon_code is None:
        return False

    if seat == '':
        try:
            apply_coupon(pos_id, table_id=table_id, coupon_code=coupon_code)
        except DiscountMinLevelException as ex:
            show_messagebox(
                posid=pos_id,
                message="$NDISCOUNT_ERROR_USER_MIN_LEVEL",
            )
            user_was_authorized, user_auth = get_authorization(
                pos_id=pos_id,
                min_level=ex.min_level_required,
                model=get_model(pos_id),
            )
            if user_was_authorized:
                user_xml_str = sysactions.get_user_information(user_number=user_auth)
                user_element = eTree.XML(user_xml_str).find("user")
                user_auth_level = int(user_element.attrib["Level"])

                apply_coupon(
                    pos_id=pos_id,
                    table_id=table_id,
                    coupon_code=coupon_code,
                    user_auth_level=user_auth_level,
                )
                return True
        return

    _apply_coupon_and_set_seat(coupon_code, pos_id, seat, table_id)


def apply_coupon(
    pos_id,                 # type: str
    table_id=None,          # type: Optional[str]
    coupon_code=None,       # type: Optional[str]
    user_auth_level=-1,     # type: Optional[int]
    benefit=None,           # type: Optional[Dict]
    applier=None,           # type: Optional[str]
):
    # type: (...) -> bool

    model = get_model(pos_id)
    order = get_current_order(model)
    message = None

    if user_auth_level == -1:
        user_auth_id = int(model.find("Operator").get("id"))
        user_auth_level = get_user_level(user_auth_id)

    try:
        originator_pos_id = int(order.get("originatorId")[3:]) if order else None
        if benefit is None:
            coupon_code = coupon_code.upper()
            data = json.dumps(dict(
                posId=pos_id,
                originatorPosId=originator_pos_id,
                couponId=coupon_code,
                user_auth_level=user_auth_level
            ))

            response = mb_context.MB_EasySendMessage("nCoupon", TK_NCOUPON_APPLY_COUPON, FM_PARAM, data)
        else:
            data = json.dumps(
                dict(
                    posId=pos_id,
                    originatorPosId=originator_pos_id,
                    benefit=benefit,
                    user_auth_level=user_auth_level,
                    applier=applier,
                )
            )

            response = mb_context.MB_EasySendMessage("nCoupon", TK_NCOUPON_APPLY_BENEFIT, FM_PARAM, data)

        if response.token == TK_SYS_ACK:
            if order and order.get('state') == "TOTALED":
                message = "$SUCCESS_COUPON_APPLIED"
            else:
                message = "$SUCCESS_NOT_TOTALED_COUPON_APPLIED"
            if table_id is not None:
                from table_actions import process_discount_in_table
                process_discount_in_table(pos_id, order, table_id)
            return True
        elif response.token == TK_NCOUPON_ERROR:
            error_code = response.data.split("\0")[0]
            if error_code == ApplyCouponErrorCode.COUPON_LOCKED:
                message = "$ERROR_COUPON_LOCKED_BY_ANOTHER_REQUEST"
            elif error_code == ApplyCouponErrorCode.COUPON_ALREADY_USED:
                message = "$ERROR_COUPON_APPLIED"
            elif error_code == ApplyCouponErrorCode.COUPON_NOT_FOUND:
                message = "$ERROR_COUPON_NOT_FOUND"
            elif error_code == ApplyCouponErrorCode.CUSTOM_ERROR:
                data_json = json.loads(response.data.split("\0")[1].decode("utf-8"))
                custom_error = _build_custom_error(data_json)
                message = _get_translated_message(
                    pos_id=pos_id,
                    custom_error=custom_error,
                )
            elif error_code == ApplyCouponErrorCode.NDISCOUNT_ERROR:
                ndiscount_error = response.data.split("\0")[1]
                ndiscount_error_code = ndiscount_error.split("\0")[0]
                if ndiscount_error_code == "NDISCOUNT_ERROR_USER_MIN_LEVEL":
                    raise DiscountMinLevelException(min_level_required=int(ndiscount_error_code.split("\0")[1]))

                raise ErrorApplyingDiscount(error_code=ndiscount_error_code)
            else:
                logger.error("Unknown error code from nCoupon: {}".format(error_code))
                message = "$ERROR_COUPON_APPLIED"
        else:
            raise UnexpectedResponse("nCoupon", response)

    except (MBTimeout, MBException) as _:
        show_messagebox(pos_id, "$ERROR_COUPON_INTERNAL_ERROR")
        logger.exception("Error sending message to nCoupon")
        raise
    except DiscountMinLevelException as _:
        raise
    except ErrorApplyingDiscount as ex:
        message = "$" + ex.error_code
        return False
    except UnexpectedResponse as ex:
        logger.error("Unexpected response from nCoupon", exc_info=ex)
        message = "$ERROR_COUPON_INTERNAL_ERROR"
        return False
    except Exception as ex:
        logger.error("Unknown error applying coupon", exc_info=ex)
        raise
    finally:
        if message:
            show_messagebox(pos_id, message)


def _apply_coupon_and_set_seat(coupon_code, pos_id, seat, table_id):
    # type: (str, str, str, str) -> None

    model = get_model(pos_id)
    pos_ot = get_posot(model)
    order_picture = eTree.XML(pos_ot.orderPicture(pos_id))
    order_id = order_picture.get("orderId")
    sale_lines = order_picture.findall(".//SaleLine")
    last_line_number = max([int(x.get("lineNumber")) for x in sale_lines]) if sale_lines else 0
    apply_coupon(pos_id, table_id=table_id, coupon_code=coupon_code)
    _set_custom_properties_for_added_sale_lines(pos_id, pos_ot, order_id, last_line_number, seat)


def _set_custom_properties_for_added_sale_lines(pos_id, pos_ot, order_id, last_line_number, seat):
    order_picture = eTree.XML(pos_ot.orderPicture(pos_id, order_id))
    sale_lines = order_picture.findall(".//SaleLine")
    last_line_number_after_apply_coupon = max([int(x.get("lineNumber")) for x in sale_lines]) if sale_lines else 0
    if last_line_number != last_line_number_after_apply_coupon:
        sale_type = None if order_picture.get("saleTypeDescr") == "EAT_IN" else order_picture.get("saleTypeDescr")

        for sale_line in sale_lines:
            if int(sale_line.get("level")) > 0 or int(sale_line.get("lineNumber")) <= int(last_line_number):
                continue

            set_custom_properties(pos_ot, sale_line, seat, sale_type)


def _build_custom_error(
    data,  # type: Dict
):
    # type: (...) -> CustomError

    message = data["message"]
    return CustomError(
        message=TranslatedMessage(
            pt=message.get("pt"),
            en=message.get("en"),
            es=message.get("es"),
        )
    )


def _get_translated_message(
    pos_id,        # type: str
    custom_error,  # type: CustomError
):
    language = _get_pos_language(pos_id=pos_id)
    if hasattr(custom_error.message, language):
        return getattr(custom_error.message, language)
    only_language = language[0:2]
    if hasattr(custom_error.message, only_language) and getattr(custom_error.message, only_language) is not None:
        return getattr(custom_error.message, only_language)

    return translate_message(
        model=get_model(pos_id),
        message_key="$ERROR_COUPON_CUSTOM_ERROR_NO_LANGUAGE",
    )


def _get_pos_language(
    pos_id,  # type: str
):
    model = get_model(posid=pos_id)
    language = model.find("Language").get("name").lower()
    return language
