import json
import logging

from xml.etree import cElementTree as eTree

from model.customexception import ErrorApplyingDiscount, DiscountMinLevelException
from typing import Optional

from actions.sale_line.set_custom_properties import set_custom_properties
from bustoken import TK_NCOUPON_APPLY_COUPON
from msgbus import FM_PARAM, TK_SYS_ACK, MBTimeout, MBException
from mw_helper import (
    show_keyboard,
)
from helper import remove_accents
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

logger = logging.getLogger("PosActions")


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

    if seat == '':
        apply_coupon(pos_id, table_id=table_id, coupon_code=coupon_code)
        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]
    raise_error=False       # type: Optional[bool]
):
    # type: (...) -> None

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

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

        response = mb_context.MB_EasySendMessage("nCoupon", TK_NCOUPON_APPLY_COUPON, FM_PARAM, data)
        if response.token != TK_SYS_ACK:
            if response.data == "NDISCOUNT_ERROR_USER_MIN_LEVEL":
                raise DiscountMinLevelException()

            exception = ErrorApplyingDiscount()
            exception.message = _format_error_message(
                model=model,
                data=response.data,
            )
            raise exception

        message = "$SUCCESS_NOT_TOTALED_COUPON_APPLIED"
        if response.token == TK_SYS_ACK and order and order.get('state') == "TOTALED":
            message = "$SUCCESS_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)

    except (MBTimeout, MBException) as _:
        show_messagebox(pos_id, "$ERROR_COUPON_APPLIED")
        logger.exception("Error sending message to nDiscount")
        raise
    except DiscountMinLevelException as _:
        logger.exception("Error applying coupon")
        raise
    except Exception as ex:
        error_message = _get_error_message_from_exception(
            exception=ex,
            model=model,
        )

        show_messagebox(pos_id, error_message)
        logger.exception("Error applying coupon")
        if raise_error:
            raise
    finally:
        if message:
            show_messagebox(pos_id, message)


def _get_error_message_from_exception(
    exception,  # type: Exception
    model,      # type: eTree.XML
):
    # type: (...) -> str

    error_message = None
    if hasattr(exception, 'message') and exception.message not in (None, "None", ""):
        error_message = translate_message(
            model=model,
            message_key=str(exception.message),
        )

    return error_message or translate_message(
        model=model,
        message_key="ERROR_COUPON_APPLIED",
    )


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 _format_error_message(
    model,    # type: eTree.Element
    data,     # type: json
):
    # type: (...) -> Optional[str]

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

    try:
        data = json.loads(data)

        if data.get("status_code") != 406:
            return data.get("message") or None

        language = model.findall("Language")[0].get("name").lower().replace("_", "-")
        data_message = data.get("message", {}).get("messages", "") if data.get("message") else {}
        translated_message = data_message.get(language, "")
        return remove_accents(text=translated_message)
    except Exception as _:
        return data

