import json
import logging
from xml.etree import cElementTree as eTree
from xml.etree.ElementTree import Element

from order_api.dto import CustomProperty

from order_api import OrderService

from api.table import get_table_service
from typing import (
    Dict,
    List,
    Optional,
    Tuple,
    Union,
)

from bustoken import TK_NCOUPON_APPLY_BENEFIT
from msgbus import FM_PARAM
from helper import (
    remove_accents,
    format_cpf_cnpj,
    format_phone,
)
from mw_helper import (
    show_check_list_box_dialog,
    show_filtered_list_box_dialog,
    show_loyalty_dialog,
    show_message_dialog,
    show_message_options_dialog,


)
from posot import OrderTaker
from sysactions import (
    action,
    get_current_order,
    get_model,
    get_posot,
    show_keyboard,
    show_messagebox,
    translate_message,
    StopAction,
)

from actions import mb_context
from actions.coupon.display_coupon import (
    get_benefits_from_order,
    remove_discounts,
)
from actions.customer import (
    get_customer_doc,
    get_customer_phone_number,
)
from actions.loyalty.model import LoyaltyButtonEnum
from application.interactor import LoyaltyInteractor
from utilconfigurations import get_ask_document_pinpad
from actions.loyalty.model import LoyaltyStopPaymentAction

from api.order import get_order_service

logger = logging.getLogger("PosActions")
title = "$INFORMATION"
loyalty_interactor = LoyaltyInteractor()
LOYALTY_ID = "LOYALTY_ID"
LOYALTY_CUSTOMER_INFO = "LOYALTY_CUSTOMER_INFO"
LOYALTY_CUSTOMER_BENEFITS = "LOYALTY_CUSTOMER_BENEFITS"
LOYALTY_COUPONS_TO_APPLY_ON_TOTAL = "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"
LOYALTY_PROPERTY_KEYS = [
    LOYALTY_ID,
    LOYALTY_CUSTOMER_INFO,
    LOYALTY_CUSTOMER_BENEFITS,
    LOYALTY_COUPONS_TO_APPLY_ON_TOTAL,
]


@action
def is_loyalty_component_available(
    _=None,     # type: str
):
    # type: (...) -> str

    loyalty_available = loyalty_interactor.is_loyalty_available()
    if loyalty_available is None:
        return ""

    return str(loyalty_available).lower()


@action
def open_loyalty(
    pos_id,                 # type: str
    table_id=None,          # type: Optional[str]
    order_id=None,          # type: Optional[str]
    table_seat="table",     # type: Optional[str]
):
    # type: (...) -> bool

    pos_id = int(pos_id)
    table_id = None if table_id in [None, "", "None"] else table_id
    order_id = None if order_id in [None, "", "None"] else int(order_id)

    while True:
        loyalty_properties = _get_loyalty_properties(
            pos_id=pos_id,
            table_id=table_id,
            order_id=order_id,
        )

        has_customer_info = False
        if loyalty_properties:
            has_customer_info = _has_customer_info(
                loyalty_properties=loyalty_properties,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )

        if has_customer_info:
            welcome_message = _get_welcome_message(
                loyalty_properties=loyalty_properties,
                order_id=order_id,
                table_id=table_id,
                table_seat=table_seat,
            )
            loyalty_id_button = "$REMOVE_LOYALTY"
            is_disabled = ""
        else:
            welcome_message, loyalty_id_button = loyalty_interactor.get_welcome_message_button_loyalty()
            is_disabled = '#DISABLED'

        buttons = "{}|$AVAILABLE_COUPONS{}|$APPLIED_COUPONS{}|$CLOSE".format(
            loyalty_id_button,
            is_disabled,
            is_disabled,
        )
        resp = show_loyalty_dialog(
            pos_id=str(pos_id),
            buttons=buttons,
            message=welcome_message,
        )

        if resp in (None, LoyaltyButtonEnum.CANCEL):
            break

        try:
            _execute_loyalty_option(
                pos_id=pos_id,
                response=resp,
                customer_info=has_customer_info,
                order_id=order_id,
                table_id=table_id,
                table_seat=table_seat,
            )
        except LoyaltyStopPaymentAction:
            pass

    return True


def _execute_loyalty_option(
    pos_id,         # type: int
    response,       # type: int
    customer_info,  # type: bool
    order_id,       # type: int
    table_id,       # type: str
    table_seat,     # type: str

):
    # type: (...) -> None

    if response == LoyaltyButtonEnum.UPDATE_CPF:
        if customer_info:
            _remove_loyalty_properties(
                pos_id=pos_id,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )
        else:
            register_loyalty_properties(
                pos_id=pos_id,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )

        return

    if response == LoyaltyButtonEnum.AVAILABLE_COUPONS:
        if customer_info:
            _show_available_coupons(
                pos_id=pos_id,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )

        return

    if response == LoyaltyButtonEnum.APPLIED_COUPONS and customer_info:
        _show_applied_coupons(
            pos_id=pos_id,
            table_id=table_id,
            order_id=order_id,
            table_seat=table_seat,
        )


def register_loyalty_properties(
    pos_id,          # type: int
    table_id,        # type: Optional[str]
    order_id,        # type: Optional[int]
    table_seat,      # type: str
):
    # type: (...) -> None

    loyalty_id = _set_loyalty_properties(
        pos_id=pos_id,
        order_id=order_id,
        table_id=table_id,
        table_seat=table_seat,
        loyalty_type_id=loyalty_interactor.get_loyalty_id_type(),
    )

    if table_id:
        _update_table_picture(
            pos_id=pos_id,
            table_id=table_id,
        )

    if not loyalty_id or not order_id:
        return

    if not _is_order_totaled(pos_id=pos_id):
        return

    _apply_totaled_benefits(
        pos_id=pos_id,
        order_id=order_id,
    )


def _apply_totaled_benefits(
    pos_id,     # type: int
    order_id,   # type: int
):
    # type: (...) -> None

    order_service = get_order_service(pos_id=pos_id)
    order = order_service.get_order(order_id=order_id)
    to_apply_on_total = []
    benefits = []
    for custom_property in order.custom_properties:
        if custom_property.key == LOYALTY_COUPONS_TO_APPLY_ON_TOTAL:
            to_apply_on_total = json.loads(custom_property.value or "[]")
        if custom_property.key == LOYALTY_CUSTOMER_BENEFITS:
            benefits = json.loads(custom_property.value or "[]")

    benefits_to_apply = list(filter(lambda b: b["id"] in to_apply_on_total, benefits))

    data = json.dumps({
        "posId": pos_id,
        "originatorPosId": pos_id,
        "benefits": benefits_to_apply,
        "orderPicture": eTree.tostring(order.order_xml),
        "isManual": False,
    })

    mb_context.MB_EasySendMessage(
        dest_name="nCoupon",
        token=TK_NCOUPON_APPLY_BENEFIT,
        format=FM_PARAM,
        data=data,
    )


def _remove_loyalty_properties_qsr(
    pos_id,     # type: int
    order_id,   # type: int
):
    # type: (...) -> None

    properties_to_remove = LOYALTY_PROPERTY_KEYS + ["CUSTOMER_NAME", "CUSTOMER_DOC"]
    get_order_service(pos_id=pos_id).remove_order_custom_properties(
        keys=properties_to_remove,
        order_id=order_id,
    )


def _remove_loyalty_properties_ts(
    pos_id,                         # type: int
    table_id,                       # type: str
    table_seat,                     # type: str
):
    # type: (...) -> None

    loyalty_properties = _get_loyalty_properties(
        pos_id=pos_id,
        table_id=table_id,
    )
    for prop in LOYALTY_PROPERTY_KEYS:
        if loyalty_properties.get(prop):
            loyalty_properties[prop].pop(table_seat, None)
            loyalty_properties[prop] = json.dumps(loyalty_properties[prop])

    if table_seat == "table":
        loyalty_properties["CUSTOMER_NAME"] = ""
        loyalty_properties["CUSTOMER_DOC"] = ""

    from msgbusboundary import MsgBusTableService as tableService

    tableService().set_table_custom_properties(
        pos_id=pos_id,
        properties=json.dumps(loyalty_properties),
        table_id=table_id,
    )
    _update_table_picture(
        pos_id=pos_id,
        table_id=table_id,
    )


def _update_table_picture(
    pos_id,     # type: int
    table_id,   # type: str
):
    # type: (...) -> None

    from msgbusboundary import MsgBusTableService as tableService
    from table_actions import create_table_modified_event

    table_picture = tableService().get_table_picture(
        pos_id=pos_id,
        table_id=table_id,
    )
    create_table_modified_event(
        pos_id=pos_id,
        table_picture=table_picture,
    )


def _get_loyalty_properties(
    pos_id,         # type: int
    table_id=None,  # type: Optional[str]
    order_id=None,  # type: Optional[int]
):
    # type: (...) -> Optional[Dict]

    if order_id:
        return _get_loyalty_properties_for_qsr(
            pos_id=pos_id,
            order_id=order_id,
        )

    if table_id:
        return _get_loyalty_properties_for_ts(
            pos_id=pos_id,
            table_id=table_id,
        )

    return None


def _get_loyalty_properties_for_qsr(
    pos_id,    # type: int
    order_id,  # type: int
):
    # type: (...) -> Optional[Dict]

    order_xml = get_order_service(pos_id=pos_id).get_order_picture(order_id=order_id)
    order_properties = order_xml.find(".//CustomOrderProperties")

    property_base = ".//OrderProperty[@key='{}']"
    customer_info = order_properties.find(property_base.format(LOYALTY_CUSTOMER_INFO))
    if customer_info is not None:
        customer_info = customer_info.get("value")

    benefits = order_properties.find(property_base.format(LOYALTY_CUSTOMER_BENEFITS))
    if benefits is not None:
        benefits = benefits.get("value")

    coupons_on_total = order_properties.find(property_base.format(LOYALTY_COUPONS_TO_APPLY_ON_TOTAL))
    if coupons_on_total is not None:
        coupons_on_total = coupons_on_total.get("value")

    return {
        LOYALTY_CUSTOMER_INFO: json.loads(customer_info) if customer_info else {},
        LOYALTY_CUSTOMER_BENEFITS: json.loads(benefits) if benefits else [],
        LOYALTY_COUPONS_TO_APPLY_ON_TOTAL: json.loads(coupons_on_total) if coupons_on_total else [],
    }


def _get_loyalty_properties_for_ts(
    pos_id,     # type: int
    table_id,   # type: str
):
    # type: (...) -> Optional[Dict]

    table_service = get_table_service(pos_id=pos_id)
    table_service_dto = table_service.get_service(table_id=table_id)
    table_properties = table_service_dto.custom_properties

    loyalty_ids = json.loads(table_properties.get(LOYALTY_ID, "{}"))

    customer_info = json.loads(table_properties.get(LOYALTY_CUSTOMER_INFO, "{}"))
    _parse_object_children(dictionary=customer_info)

    benefits = json.loads(table_properties.get(LOYALTY_CUSTOMER_BENEFITS, "{}"))
    _parse_object_children(dictionary=benefits)

    coupons_on_total = json.loads(table_properties.get(LOYALTY_COUPONS_TO_APPLY_ON_TOTAL, "{}"))
    _parse_object_children(dictionary=coupons_on_total)

    return {
        LOYALTY_ID: loyalty_ids,
        LOYALTY_CUSTOMER_INFO: customer_info,
        LOYALTY_CUSTOMER_BENEFITS: benefits,
        LOYALTY_COUPONS_TO_APPLY_ON_TOTAL: coupons_on_total,
    }


def _parse_object_children(
    dictionary,     # type: Dict
):
    # type: (...) -> None

    for key, value in dictionary.items():
        if isinstance(value, str):
            dictionary.update({key: json.loads(value)})


def _remove_loyalty_properties(
    pos_id,        # type: int
    table_id,      # type: Optional[str]
    order_id,      # type: Optional[int]
    table_seat,    # type: str
):
    # type: (...) -> None

    model = get_model(posid=pos_id)
    if _has_discounts_on_total(
        pos_id=pos_id,
        model=model,
    ):
        return

    if order_id:
        _remove_loyalty_properties_qsr(
            order_id=order_id,
            pos_id=pos_id,
        )

    if table_id:
        _remove_loyalty_properties_ts(
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
        )


def _set_loyalty_properties(
    pos_id,             # type: int
    loyalty_type_id,    # type: str
    table_id,           # type: Optional[str]
    order_id,           # type: Optional[int]
    table_seat,         # type: str
):
    # type: (...) -> Optional[str]

    model = get_model(posid=pos_id)
    pos_ot = get_posot(model=model)
    loyalty_id = get_user_input_by_type(
        pos_id=pos_id,
        pos_ot=pos_ot,
        loyalty_type_id=loyalty_type_id,
    )

    try:
        loyalty_customer_info = loyalty_interactor.set_loyalty_info(
            pos_id=pos_id,
            loyalty_id=loyalty_id,
            order_id=order_id,
            table_id=table_id,
            seat=table_seat,
        )
    except LoyaltyInteractor.LoyaltyPartnerError:
        logger.exception("Partner error")
        show_messagebox(
            posid=pos_id,
            message="$LOYALTY_PARTNER_ERROR",
        )
        return None

    if loyalty_customer_info is None:
        show_messagebox(
            posid=pos_id,
            message="$LOYALTY_INFORMATION_NOT_FOUND",
        )
        return None

    return loyalty_id


def _has_customer_info(
    loyalty_properties,     # type: Dict
    table_id,               # type: Optional[str]
    order_id,               # type: Optional[int]
    table_seat,             # type: str
):
    # type: (...) -> bool

    if order_id:
        customer_info = loyalty_properties.get(LOYALTY_CUSTOMER_INFO)
        return bool(customer_info)

    if table_id:
        customer_info = loyalty_properties.get(LOYALTY_CUSTOMER_INFO)
        customer_info = customer_info.get(table_seat, {})
        return bool(customer_info)

    return False


def _get_welcome_message(
    loyalty_properties,     # type: Dict
    order_id,               # type: Optional[int]
    table_id,               # type: Optional[str]
    table_seat,             # type: str
):
    # type: (...) -> unicode

    customer_info = loyalty_properties.get(LOYALTY_CUSTOMER_INFO, {})
    if table_id and not order_id:
        customer_info = customer_info.get(table_seat, {})

    customer_info = customer_info.get("customerInfo", {})
    customer = customer_info.get("customer", {})
    customer_name = customer.get("name") or "-"
    customer_doc = format_cpf_cnpj(customer.get("document")) or "-" if "document" in customer else "-"
    customer_phone = format_phone(customer.get("phone")) or "-" if "phone" in customer else "-"

    additional_info = customer_info.get("additionalInfo", {})
    welcome_message = additional_info.get("welcomeMessage", "")

    return "{}|{}|{}|{}|{}".format(
        "$WELCOME_CLIENT_LOYALTY_INFO",
        customer_name.encode("utf-8"),
        customer_doc,
        customer_phone,
        welcome_message.encode('utf-8'),
    )


def _show_available_coupons(
    pos_id,         # type: int
    table_id,       # type: str
    order_id,       # type: Optional[int]
    table_seat,     # type: str
):
    # type: (...) -> None

    loyalty_properties = _get_loyalty_properties(
        pos_id=pos_id,
        table_id=table_id,
        order_id=order_id,
    )
    if not loyalty_properties:
        return

    customer_benefits, coupons_to_apply_on_total = _get_loyalty_coupons(
        loyalty_properties=loyalty_properties,
        order_id=order_id,
        table_seat=table_seat,
    )

    available_coupons = _get_available_coupons(
        pos_id=pos_id,
        customer_benefits=customer_benefits,
        coupons_to_apply_on_total=coupons_to_apply_on_total,
    )
    if not available_coupons:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$WITHOUT_ANY_LOYALTY_BENEFITS_TO_SHOW",
        )
        return

    options = [
        u"{} - {}".format(index + 1, coupon["coupon"]["name"]).encode("utf-8")
        for index, coupon in enumerate(available_coupons)
    ]
    selected_index = show_filtered_list_box_dialog(
        pos_id=str(pos_id),
        options=options,
        title="$SELECT_ONE_COUPON",
        mask="NOFILTER",
    )

    if selected_index is None:
        return

    if order_id:
        _save_selected_benefit_in_order(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            benefit_id=available_coupons[selected_index]["benefitId"],
            order_id=order_id,
        )
    else:
        _save_selected_benefit_in_table(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            benefit_id=available_coupons[selected_index]["benefitId"],
            table_id=table_id,
            table_seat=table_seat,
        )
    raise StopAction()


def _get_loyalty_coupons(
    loyalty_properties,             # type: Dict
    order_id,                       # type: Optional[int]
    table_seat,                     # type: str
    validate_from_source=False,     # type: Optional[bool]
):
    # type: (...) -> Tuple[Dict[str, Union[str, List, Dict]], List[str]]

    if order_id:
        customer_benefits = loyalty_properties.get(LOYALTY_CUSTOMER_BENEFITS, [])
        coupons_to_apply_on_total = loyalty_properties.get(LOYALTY_COUPONS_TO_APPLY_ON_TOTAL, [])
        if not coupons_to_apply_on_total and validate_from_source:
            coupons_to_apply_on_total = [b.get("id") for b in customer_benefits if b.get("automatic") == "ON_TOTAL"]

        return customer_benefits, coupons_to_apply_on_total

    customer_benefits = loyalty_properties.get(LOYALTY_CUSTOMER_BENEFITS, {})
    customer_benefits = customer_benefits.get(table_seat, [])
    coupons_to_apply_on_total = loyalty_properties.get(LOYALTY_COUPONS_TO_APPLY_ON_TOTAL, {})
    coupons_to_apply_on_total = coupons_to_apply_on_total.get(table_seat, [])

    return customer_benefits, coupons_to_apply_on_total


def _show_applied_coupons(
    pos_id,         # type: int
    table_id,       # type: Optional[str]
    order_id,       # type: Optional[int]
    table_seat,     # type: str
):
    # type: (...) -> None

    loyalty_properties = _get_loyalty_properties(
        pos_id=pos_id,
        table_id=table_id,
        order_id=order_id,
    )

    customer_benefits, coupons_to_apply_on_total = _get_loyalty_coupons(
        loyalty_properties=loyalty_properties,
        order_id=order_id,
        table_seat=table_seat,
    )
    if not coupons_to_apply_on_total:
        show_messagebox(
            posid=pos_id,
            message="$ORDER_HAS_NO_APPLIED_DISCOUNT",
        )
        return

    coupon_options = _format_coupon_options(
        pos_id=pos_id,
        coupon_options=customer_benefits,
        coupons_to_apply_on_total=coupons_to_apply_on_total,
    )
    options = ["{0}".format(c["name"]) for c in coupon_options]
    if len(options) <= 1:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$WITHOUT_ANY_LOYALTY_BENEFITS_TO_SHOW",
        )
        return

    selected_indexes = show_check_list_box_dialog(
        pos_id=pos_id,
        options=options,
        title="$SELECT_ONE_COUPON",
        buttons="$REMOVE|$CANCEL",
        mask="NO_SALE_PANEL_IN_LOYALTY_BENEFITS",
    )
    if not len(selected_indexes):
        return

    loyalty_property = loyalty_properties[LOYALTY_COUPONS_TO_APPLY_ON_TOTAL]
    if not order_id:
        loyalty_property = loyalty_property[table_seat]

    indexes = [int(i) for i in selected_indexes if int(i)]
    for coupon_item in coupon_options:
        if coupon_item.get("index", 0) not in indexes:
            continue

        if order_id:
            _remove_loyalty_discount(
                pos_id=pos_id,
                order_id=order_id,
                coupon_item=coupon_item,
            )
        elif loyalty_property:
            loyalty_property.remove(coupon_item.get("id"))

    if not order_id:
        from msgbusboundary import MsgBusTableService as tableService

        tableService().set_table_custom_property(
            pos_id=pos_id,
            prop=LOYALTY_COUPONS_TO_APPLY_ON_TOTAL,
            prop_value=json.dumps(loyalty_properties[LOYALTY_COUPONS_TO_APPLY_ON_TOTAL]),
            table_id=table_id,
        )

    if _is_order_totaled(pos_id=pos_id):
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$ADD_ITEM_DISCOUNT_NOT_REMOVED",
        )


def _format_coupon_options(
    pos_id,                     # type: int
    coupon_options,             # type: Dict
    coupons_to_apply_on_total,  # type: List
):
    # type: (...) -> List

    model = get_model(posid=pos_id)
    all_coupons = translate_message(model, "ALL")
    applied_coupons = []
    benefit_index = 1

    order_benefits_ids = _get_order_benefits_ids_dict(model=model)
    is_order_totaled = _is_order_totaled(pos_id=pos_id)

    for coupon in coupon_options:
        benefit_id = coupon.get('id')
        benefit_key = order_benefits_ids[benefit_id] if benefit_id in order_benefits_ids.keys() else None
        add_item = _has_item_to_add(coupon=coupon)
        is_coupon_applied = benefit_id not in order_benefits_ids.keys()

        if (benefit_id not in coupons_to_apply_on_total) or (is_order_totaled and is_coupon_applied):
            continue

        disable = False
        description = (coupon.get("userDescription") or coupon.get("name") or coupon.get("id") or "").encode("UTF-8")
        name = "{}##{}##{}".format(description, 'null', disable)
        data = {
            "index": benefit_index,
            "id": benefit_id,
            "name": name,
            "benefit_key": benefit_key,
            "add_item": add_item
        }
        applied_coupons.append(data)
        benefit_index += 1

    name = "{}##{}##False".format(all_coupons, 'null')
    data = {"index": 0, "id": None, "name": name}
    applied_coupons.append(data)
    applied_coupons.sort(key=lambda x: (x["index"]))
    return applied_coupons


def _save_selected_benefit_in_table(
    loyalty_properties,     # type: Dict[str, Dict]
    pos_id,                 # type: int
    benefit_id,             # type: str
    table_id,               # type: Optional[str]
    table_seat,             # type: Optional[str]
):
    # type: (...) -> None

    coupons_to_apply_on_total = loyalty_properties[LOYALTY_COUPONS_TO_APPLY_ON_TOTAL]
    if table_seat in coupons_to_apply_on_total:
        coupons_to_apply_on_total[table_seat].append(benefit_id)
    else:
        coupons_to_apply_on_total[table_seat] = [benefit_id]

    from msgbusboundary import MsgBusTableService as tableService
    tableService().set_table_custom_property(
        pos_id=pos_id,
        prop=LOYALTY_COUPONS_TO_APPLY_ON_TOTAL,
        prop_value=json.dumps(coupons_to_apply_on_total),
        table_id=table_id,
    )

    show_messagebox(
        posid=pos_id,
        message="$APPLIED_DISCOUNTS_MUST_BE_APPLIED_WHEN_TOTAL",
    )


def _save_selected_benefit_in_order(
    loyalty_properties,     # type: Dict[str, Union[Dict, List]]
    pos_id,                 # type: int
    benefit_id,             # type: str
    order_id,               # type: int
):
    # type: (...) -> None

    coupons_to_apply_on_total = loyalty_properties[LOYALTY_COUPONS_TO_APPLY_ON_TOTAL]
    if coupons_to_apply_on_total is not None:
        coupons_to_apply_on_total.append(benefit_id)
    else:
        coupons_to_apply_on_total = [benefit_id]

    get_order_service(pos_id=pos_id).set_order_custom_property(
        order_id=order_id,
        custom_property=CustomProperty(
            key=LOYALTY_COUPONS_TO_APPLY_ON_TOTAL,
            value=json.dumps(coupons_to_apply_on_total),
        )
    )

    show_messagebox(
        posid=pos_id,
        message="$APPLIED_DISCOUNTS_MUST_BE_APPLIED_WHEN_TOTAL",
    )


def _is_tab(
    table_id,   # str
):
    # type: (...) -> bool

    return 'TAB' in table_id


def _get_available_coupons(
    pos_id,                     # type: int
    customer_benefits,          # type: List
    coupons_to_apply_on_total,  # type: List
):
    # type: (...) -> List[Dict]

    benefit_list = []
    model = get_model(posid=pos_id)
    order_benefits = _get_applied_benefit_on_order(model=model)
    order_benefits_ids = [benefit["benefit"]["id"] for benefit in order_benefits]
    automatic_benefit = remove_accents(translate_message(model, "AUTOMATIC_BENEFIT"))
    is_order_totaled = _is_order_totaled(pos_id=pos_id)

    for index, benefit in enumerate(customer_benefits):
        applied_benefit = _validate_applied_benefit(
            benefit=benefit,
            coupons_to_apply_on_total=coupons_to_apply_on_total,
            order_benefits_ids=order_benefits_ids,
            is_order_totaled=is_order_totaled,
        )

        if applied_benefit:
            continue

        name = str(benefit.get("userDescription", benefit.get("name", benefit.get("id", ""))).encode("utf-8"))
        is_automatic = benefit.get('automatic', None) not in [None, 'DISABLED']

        if name.startswith("$"):
            params = name.split("|", 1)[1] if "|" in name else ""
            message = name.split("|", 1)[0] if params else name
            name = translate_message(model, message[1:], *(params.split("|")))

        if is_order_totaled and is_automatic:
            name = name + " " + ("({0})".format(automatic_benefit))

        benefit_item = {
            "coupon": benefit,
            "name": name,
            "benefitId": benefit.get("id"),
        }
        benefit_list.append(benefit_item)

    return benefit_list


def _is_order_totaled(
    pos_id=None,     # type: int
    order=None,      # type: Element
):
    # type: (...) -> bool

    if not order:
        model = get_model(posid=pos_id)
        order = get_current_order(model=model)
    return order is not None and order.get("state") == "TOTALED"


def _get_order_benefits_ids_dict(
    model,     # type: Element
):
    # type: (...) -> Dict

    order_benefits = _get_applied_benefit_on_order(model=model)
    benefits_dict = {}
    for _, benefit in enumerate(order_benefits):
        benefits_dict.update({benefit["benefit"]["id"]: benefit["benefit_key"]})
    return benefits_dict


def _has_item_to_add(
    coupon,     # type: Dict
):
    # type: (...) -> Optional[bool]

    coupon_actions = coupon.get("actions")
    for coupon_action in coupon_actions.values():
        if coupon_action.get("itemsToAdd"):
            return True


def _remove_loyalty_discount(
    pos_id,                 # type: int
    order_id,               # type: int
    coupon_item,            # type: Dict[str, Union[int, str]]
):
    # type: (...) -> None

    order_service = get_order_service(pos_id=pos_id)
    order = order_service.get_order_picture(order_id=order_id)
    coupon_key = _get_applied_benefit_key(
        order=order,
        coupon=coupon_item,
    )
    if coupon_key:
        remove_discounts(
            pos_id=pos_id,
            coupon_ids=[coupon_key],
        )
    else:
        _remove_from_loyalty_on_total_qsr(
            order_service=order_service,
            order=order,
            benefit_id=coupon_item.get("id"),
        )


def _get_applied_benefit_key(
    order,      # type: eTree.Element
    coupon,     # type: str
):
    # type: (...) -> Optional[str]

    if coupon.get("benefit_key"):
        return coupon["benefit_key"]

    benefit_list_custom_property = order.find(".//OrderProperty[@key='BENEFIT_LIST']")
    if benefit_list_custom_property is None:
        return None

    benefit_list_json = json.loads(benefit_list_custom_property.get("value"))
    benefit_json = benefit_list_json.get("benefitList")
    benefit_list = json.loads(benefit_json)
    for benefit in benefit_list:
        if benefit["benefit"]["id"] == coupon.get("id"):
            return benefit["benefit_key"]

    return None


def _remove_from_loyalty_on_total_qsr(
    order_service,  # type: OrderService
    order,          # type: eTree.Element
    benefit_id,     # type: str
):
    # type: (...) -> None

    prop = order.find(".//OrderProperty[@key='{}']".format(LOYALTY_COUPONS_TO_APPLY_ON_TOTAL))
    if prop is None:
        return

    loyalty_on_total = json.loads(prop.get("value") or "[]")
    new_loyalty_on_total = []
    for current_benefit_id in loyalty_on_total:
        if current_benefit_id != benefit_id:
            new_loyalty_on_total.append(current_benefit_id)

    order_service.set_order_custom_property(custom_property=CustomProperty(
        key=LOYALTY_COUPONS_TO_APPLY_ON_TOTAL,
        value=json.dumps(new_loyalty_on_total),
    ))


def _validate_applied_benefit(
    benefit,                    # type: Dict
    coupons_to_apply_on_total,  # type: List
    order_benefits_ids,         # type: List
    is_order_totaled,           # type: bool
):
    # type: (...) -> bool

    applied_benefit = benefit.get("id") in coupons_to_apply_on_total
    benefit_applied_in_order = benefit.get("id") in order_benefits_ids
    if is_order_totaled:
        applied_benefit = applied_benefit and benefit_applied_in_order

    return applied_benefit


def _get_applied_benefit_on_order(
    model,     # type: Element
):
    # type: (...) -> Dict

    pos_ot = get_posot(model=model)
    order = get_current_order(model=model)
    order_benefits = get_benefits_from_order(
        pos_ot=pos_ot,
        order=order,
    )
    return order_benefits


def _has_discounts_on_total(
    pos_id,     # type: int
    model,      # type: Element
):
    # type: (...) -> bool

    order_benefits = _get_applied_benefit_on_order(model=model)
    is_order_totaled = _is_order_totaled(pos_id=pos_id)
    if order_benefits and is_order_totaled:
        show_message_dialog(
            pos_id=pos_id,
            title=title,
            message="$CANNOT_REMOVE_LOYALTY_ON_TOTALED_SCREEN",
        )
        return True


def get_user_input_by_type(
    pos_id,             # type: int
    pos_ot,             # type: OrderTaker
    loyalty_type_id,    # type: str
):
    # type: (...) -> str

    if loyalty_type_id == "DOCUMENT":
        return _get_user_document_input(
            pos_id=pos_id,
            pos_ot=pos_ot,
            default_doc="",
            stop_action=False,
        )

    if loyalty_type_id == "PHONE":
        return _get_user_phone_input(
            pos_id=pos_id,
            stop_action=False,
        )

    if loyalty_type_id == "OTHER":
        return _get_user_other_input(pos_id=pos_id)


def _get_user_phone_input(
    pos_id,         # type: int
    stop_action,    # type: bool
):
    # type: (...) -> str

    loyalty_id = get_customer_phone_number(
        pos_id=pos_id,
        stop_action=stop_action,
    )

    if loyalty_id is None:
        raise LoyaltyStopPaymentAction()

    return loyalty_id


def _get_user_document_input(
    pos_id,         # type: int
    pos_ot,         # type: OrderTaker
    default_doc,    # type: str
    stop_action,    # type: bool
):
    # type: (...) -> str

    document_pin_pad = get_ask_document_pinpad(pos_id=pos_id)
    loyalty_id = get_customer_doc(
        pos_id=pos_id,
        pos_ot=pos_ot,
        default_doc=default_doc,
        ask_document_pinpad=document_pin_pad,
        stop_action=stop_action,
    )
    if loyalty_id is None:
        raise LoyaltyStopPaymentAction()

    return loyalty_id


def _get_user_other_input(
    pos_id,     # type: int
):
    # type: (...) -> str

    loyalty_id = show_keyboard(
        posid=pos_id,
        message="$ENTER_LOYALTY_ID",
        buttons="$OK|$NOT_INFORM|$PREVIOUS",
        defvalue="",
        title="",
    )
    if loyalty_id.strip() in ("", "NOT_INFORM"):
        return ""

    if loyalty_id is None:
        raise LoyaltyStopPaymentAction()

    return loyalty_id.translate(None, '-+[]/;.|<>:?{}=_()*&^%$#@!,').upper()


def get_table_loyalty_customer_attribute(
    pos_id,                 # type: str
    table_id,               # type: str
    attribute="document",   # type: Optional[str]
):
    # type: (...) -> str

    if table_id is None:
        return ''

    from msgbusboundary import MsgBusTableService as tableService

    loyalty_info = tableService().get_table_custom_property(
        pos_id=pos_id,
        prop=LOYALTY_CUSTOMER_INFO,
        table_id=table_id,
    )
    if loyalty_info:
        loyalty_info = json.loads(loyalty_info.get(LOYALTY_CUSTOMER_INFO))
        customer = loyalty_info.get("table", {}).get("customerInfo", {}).get("customer", {})
        return customer.get(attribute, "")
    return ""


def get_order_loyalty_customer_attribute(
    pos_id,                 # type: str
    order_picture,          # type: Element
    attribute="document",   # type: Optional[str]
):
    # type: (...) -> str

    loyalty_info = order_picture.find(".//OrderProperty[@key='{}']".format(LOYALTY_CUSTOMER_INFO))
    table_id = order_picture.find(".//OrderProperty[@key='TABLE_ID']")
    if loyalty_info is not None:
        return json.loads(loyalty_info.get("value")).get("customerInfo", {}).get("customer", {}).get(attribute, "")
    elif table_id is not None:
        return get_table_loyalty_customer_attribute(
            pos_id=pos_id,
            table_id=table_id.get("value"),
            attribute=attribute,
        )
    return ""


def get_document_from_loyalty_or_customer_response(
    pos_id,                     # type: str
    pos_ot,                     # type: OrderTaker
    ask_document_pinpad=False,  # type: bool
    document=None,              # type: Optional[str]
    stop_action=True,           # type: Optional[bool]
):
    # type: (...) -> Optional[str]

    if document is not None:
        return None

    order_picture = eTree.XML(pos_ot.orderPicture(posid=pos_id))
    property_document = order_picture.find(".//CustomOrderProperties/OrderProperty[@key='CUSTOMER_DOC']")
    if property_document is not None and property_document.get("value"):
        return None

    is_valid_document = get_order_loyalty_customer_attribute(
        pos_id=pos_id,
        order_picture=order_picture,
        attribute="isValidDocument",
    )

    if is_valid_document:
        loyalty_document = get_order_loyalty_customer_attribute(
            pos_id=pos_id,
            order_picture=order_picture,
        )
        buttons = "$CONFIRM|$OTHER|$WITHOUT_DOCUMENT"
        message = "$CONFIRM_CPF_CNPJ|{0}".format(format_cpf_cnpj(value=loyalty_document))
        resp = show_message_options_dialog(
            pos_id=pos_id,
            buttons=buttons,
            title="$CONFIRMATION",
            message=message,
        )
        if resp == 2 or resp is None:
            return ""
        if resp == 0:
            return loyalty_document

    return get_customer_doc(
        pos_id=pos_id,
        pos_ot=pos_ot,
        ask_document_pinpad=ask_document_pinpad,
        stop_action=stop_action,
    ) or ""
