import json
import logging
from datetime import datetime, timedelta
from threading import Lock
from xml.etree import cElementTree as eTree
from xml.etree.ElementTree import Element

from typing import (
    Dict,
    List,
    Optional,
    Tuple,
)

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.coupon.display_coupon import (
    get_benefits_from_order,
    remove_discounts,
)
from actions.customer import (
    get_customer_doc,
    get_customer_phone_number,
)
from actions.discount.apply_discount import apply_discount
from actions.loyalty.model import LoyaltyButtonEnum
from application.interactor import LoyaltyInteractor
from utilconfigurations import get_ask_document_pinpad
from utilfunctions import parse_loyalty_customer_info

loyalty_available_cache = None
last_cache_time = None  # type: Optional[int]
lock = Lock()
logger = logging.getLogger("PosActions")
title = "$INFORMATION"
loyalty_interactor = LoyaltyInteractor()


@action
def is_loyalty_component_available(
    pos_id,                         # type: str
    seconds_to_renew_cache='3600',  # type: str
):
    # type: (...) -> str

    global last_cache_time
    global loyalty_available_cache

    with lock:
        now = datetime.now()
        seconds_to_renew = int(seconds_to_renew_cache or '1800')
        renew_cache = not last_cache_time or now > last_cache_time + timedelta(seconds=seconds_to_renew)
        if loyalty_available_cache is None or renew_cache:
            loyalty_available_cache = loyalty_interactor.test_connection()
            last_cache_time = now
        return str(loyalty_available_cache).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

    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,
                table_id=table_id,
                order_id=order_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=pos_id,
            buttons=buttons,
            message=welcome_message,
        )

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

        _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,
        )

    return True


def _execute_loyalty_option(
    pos_id,         # type: str
    response,       # type: int
    customer_info,  # type: bool
    order_id,       # type: str
    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:
            set_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 _get_table_customer_doc(
        pos_id,         # type: str
        table_id,       # type: str
        table_seat,     # type: str
):
    # type: (...) -> Optional[str]

    from msgbusboundary import MsgBusTableService as tableService

    customer_doc = tableService().get_table_custom_property(
        pos_id=pos_id,
        prop="LOYALTY_TABLE_CUSTOMER_INFO",
        table_id=table_id,
    )
    if customer_doc:
        customer_doc = json.loads(s=customer_doc['LOYALTY_TABLE_CUSTOMER_INFO'])
        if customer_doc.get(table_seat, {}):
            customer_doc = customer_doc[table_seat].get('customer_doc', None)
        else:
            customer_doc = None

    return customer_doc


def set_loyalty_properties(
    pos_id,          # type: str
    table_id,        # type: str
    order_id,        # type: str
    table_seat,      # type: str
    loyalty_id="",     # type: Optional[str]
):
    # type: (...) -> None

    loyalty_type_id = loyalty_interactor.get_loyalty_id_type()
    if order_id not in [None, ""]:
        loyalty_properties, loyalty_id = build_loyalty_properties(
            pos_id=pos_id,
            order_id=order_id,
            table_seat=table_seat,
            loyalty_id=loyalty_id,
            loyalty_type_id=loyalty_type_id,
        )

        if not loyalty_id:
            return

        _persist_loyalty_properties_qsr(
            loyalty_properties=loyalty_properties,
            loyalty_id=loyalty_id,
            pos_id=pos_id,
            order_id=order_id,
        )

        if not loyalty_properties:
            coupons_to_apply_on_total = []
        else:
            _, coupons_to_apply_on_total, _ = _get_loyalty_coupons(
                loyalty_properties=loyalty_properties,
                order_id=order_id,
                validate_from_source=True,
            )

        is_order_totaled = _is_order_totaled(pos_id=pos_id)
        if is_order_totaled and coupons_to_apply_on_total:
            show_message_dialog(
                pos_id=pos_id,
                title=title,
                message="$AUTOMATIC_BENEFITS_TO_MANUAL",
            )

    elif table_id is not None:
        loyalty_properties, loyalty_id = build_loyalty_properties(
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
            loyalty_id=loyalty_id,
            loyalty_type_id=loyalty_type_id,
        )

        if not loyalty_id:
            return

        persist_loyalty_properties_ts(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
        )


def _persist_loyalty_properties_qsr(
    loyalty_properties,             # type: Dict
    loyalty_id,                     # type: str
    pos_id,                         # type: str
    order_id,                       # type: str
    only_discounts_on_total=False,  # type: bool
):
    # type: (...) -> None

    props = dict(LOYALTY_ID=loyalty_id or "clear")
    customer_info = loyalty_properties.get("LOYALTY_CUSTOMER_INFO", {})
    if not only_discounts_on_total:
        props["LOYALTY_CUSTOMER_INFO"] = json.dumps(customer_info) if customer_info else "clear"

        benefits = loyalty_properties.get("LOYALTY_CUSTOMER_BENEFITS")
        props["LOYALTY_CUSTOMER_BENEFITS"] = json.dumps(benefits) if benefits else "clear"

    coupons_on_total = loyalty_properties.get("LOYALTY_COUPONS_TO_APPLY_ON_TOTAL", {})
    props["LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"] = json.dumps(coupons_on_total) if coupons_on_total else "clear"

    if isinstance(customer_info, basestring):
        customer_info = json.loads(customer_info)
    customer = customer_info.get("customerInfo", {}).get("customer", {})
    name = customer.get("name", "").encode("utf-8")
    props["CUSTOMER_NAME"] = name or "clear"

    model = get_model(posid=pos_id)
    pos_ot = get_posot(model=model)
    pos_ot.setOrderCustomProperties(
        properties=props,
        orderid=order_id,
    )


def persist_loyalty_properties_ts(
    loyalty_properties,             # type: Dict
    pos_id,                         # type: str
    table_id,                       # type: str
    table_seat,                     # type: str
    only_discounts_on_total=False,  # type: bool
):
    # type: (...) -> None

    from msgbusboundary import MsgBusTableService as tableService
    from table_actions import create_table_modified_event

    table_custom_properties = {}
    if not only_discounts_on_total:
        customer_info = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_INFO", {})
        customer_info_value = json.dumps(customer_info) if customer_info else "{}"
        table_custom_properties['LOYALTY_TABLE_CUSTOMER_INFO'] = customer_info_value

        benefits = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_BENEFITS")
        benefits_value = json.dumps(benefits) if benefits else "{}"
        table_custom_properties['LOYALTY_TABLE_CUSTOMER_BENEFITS'] = benefits_value

        loyalty_ids = loyalty_properties.get("LOYALTY_TABLE_ID")
        loyalty_ids_value = json.dumps(loyalty_ids) if loyalty_ids else "{}"
        table_custom_properties['LOYALTY_TABLE_ID'] = loyalty_ids_value

    coupons_on_total = loyalty_properties.get("LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL")
    coupons_on_total_value = json.dumps(coupons_on_total) if coupons_on_total else "{}"
    table_custom_properties['LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL'] = coupons_on_total_value

    table_customer = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_INFO", {})
    if isinstance(table_customer, basestring):
        table_customer = json.loads(table_customer)
    customer_info = table_customer.get(table_seat, {}).get("customerInfo", {})

    if not only_discounts_on_total and (table_seat == "table" or _is_tab(table_id=table_id)):
        customer = customer_info.get("customer", {})
        name = customer.get("name", "").encode("utf-8")
        table_custom_properties['CUSTOMER_NAME'] = name

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

    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: str
    table_id=None,  # type: Optional[str]
    order_id=None,  # type: Optional[str]
):
    # 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: str
    order_id,  # type: str
):
    # type: (...) -> Optional[Dict]

    model = get_model(pos_id)
    pos_ot = get_posot(model)
    order_xml = pos_ot.orderPicture(
        posid=pos_id,
        orderid=order_id,
    )
    order_picture_properties = eTree.XML(order_xml).find(".//CustomOrderProperties")

    customer_info = order_picture_properties.find(".//OrderProperty[@key='LOYALTY_CUSTOMER_INFO']")
    benefits = order_picture_properties.find(".//OrderProperty[@key='LOYALTY_CUSTOMER_BENEFITS']")
    coupons_on_total = order_picture_properties.find(".//OrderProperty[@key='LOYALTY_COUPONS_TO_APPLY_ON_TOTAL']")
    if customer_info is not None:
        customer_info = customer_info.get('value')
    if benefits is not None:
        benefits = benefits.get('value')
    if coupons_on_total is not None:
        coupons_on_total = coupons_on_total.get('value')

    customer_info = json.loads(customer_info) if customer_info else []
    benefits = json.loads(benefits) if benefits else []
    coupons_on_total = json.loads(coupons_on_total) if coupons_on_total else []

    loyalty_properties = {
        "LOYALTY_CUSTOMER_INFO": customer_info,
        "LOYALTY_CUSTOMER_BENEFITS": benefits,
        "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL": coupons_on_total,
    }
    return loyalty_properties


def parse_table_loyalty_customer_info(
    loyalty_ids,         # type: str
    customer_info,       # type: str
    benefits,            # type: str
    coupons_on_total,    # type: str
):
    # type: (...) -> dict

    if loyalty_ids:
        loyalty_ids = json.loads(loyalty_ids)

    if customer_info:
        customer_info = json.loads(customer_info)
        _parse_object_children(dictionary=customer_info)

    if benefits:
        benefits = json.loads(benefits)
        _parse_object_children(dictionary=benefits)

    if coupons_on_total:
        coupons_on_total = json.loads(coupons_on_total)
        _parse_object_children(dictionary=coupons_on_total)

    loyalty_properties = {
        "LOYALTY_TABLE_ID": loyalty_ids,
        "LOYALTY_TABLE_CUSTOMER_INFO": customer_info,
        "LOYALTY_TABLE_CUSTOMER_BENEFITS": benefits,
        "LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL": coupons_on_total,
    }
    return loyalty_properties


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

    from msgbusboundary import MsgBusTableService as tableService

    table_xml = tableService().get_table_picture(
        pos_id=pos_id,
        table_id=table_id,
    )
    table_properties = eTree.XML(table_xml).find('Properties')
    loyalty_ids = table_properties.get('LOYALTY_TABLE_ID') or '{}'
    customer_info = table_properties.get('LOYALTY_TABLE_CUSTOMER_INFO') or '{}'
    benefits = table_properties.get('LOYALTY_TABLE_CUSTOMER_BENEFITS') or '{}'
    coupons_on_total = table_properties.get('LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL') or '{}'

    return parse_table_loyalty_customer_info(
        loyalty_ids=loyalty_ids,
        customer_info=customer_info,
        benefits=benefits,
        coupons_on_total=coupons_on_total,
    )


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

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


def _remove_loyalty_properties(
    pos_id,        # type: str
    table_id,      # type: str
    order_id,      # type: str
    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 not in [None, ""]:
        _persist_loyalty_properties_qsr(
            loyalty_properties={},
            loyalty_id="",
            order_id=order_id,
            pos_id=pos_id,
        )

    if table_id not in [None, ""]:
        loyalty_properties = _get_loyalty_properties(
            pos_id=pos_id,
            table_id=table_id,
            order_id=order_id,
        )

        for property_name, value in loyalty_properties.items():
            if table_seat in value:
                loyalty_properties[property_name].pop(table_seat)

        persist_loyalty_properties_ts(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat
        )


def migrate_order_loyalty_info_to_table(
    pos_id,                 # type: str
    order,                  # type: eTree.XML
    table_id,               # type: str
    table_seat="table",     # type: Optional[str]
):
    # type: (...) -> None

    table_loyalty_properties = _get_loyalty_properties_for_ts(
        pos_id=pos_id,
        table_id=table_id,
    )

    loyalty_id = order.find(".//CustomOrderProperties/OrderProperty[@key='LOYALTY_ID']")
    if loyalty_id is not None:
        table_loyalty_properties["LOYALTY_TABLE_ID"].update({table_seat: loyalty_id.get("value")})

    customer_info = order.find(".//CustomOrderProperties/OrderProperty[@key='LOYALTY_CUSTOMER_INFO']")
    if customer_info is not None:
        table_loyalty_properties["LOYALTY_TABLE_CUSTOMER_INFO"].update({
            table_seat: json.loads(customer_info.get("value"))
        })

    customer_benefits = order.find(".//CustomOrderProperties/OrderProperty[@key='LOYALTY_CUSTOMER_BENEFITS']")

    if customer_benefits is not None:
        benefits = json.loads(customer_benefits.get("value"))
        table_loyalty_properties["LOYALTY_TABLE_CUSTOMER_BENEFITS"].update({table_seat: benefits})
        apply_on_total = [b.get("id") for b in benefits if b.get("automatic") == "ON_TOTAL"]

        applied_discounts = order.find(".//CustomOrderProperties/OrderProperty[@key='BENEFIT_LIST']")
        applied_discounts = json.loads(applied_discounts.get("value") or "{}") if applied_discounts is not None else {}
        applied_discounts = applied_discounts.get("benefitList", None)
        benefits = json.loads(applied_discounts or "[]") if applied_discounts else []

        applied_loyalty_benefits = []
        for benefit_detail in benefits:
            benefit = benefit_detail.get("benefit", {})
            benefit_id = benefit.get("id", None)
            if benefit.get("is_loyalty_benefit", False) and benefit_id and benefit_id not in apply_on_total:
                applied_loyalty_benefits.append(benefit_id)
        apply_on_total.extend(applied_loyalty_benefits)

        table_loyalty_properties["LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL"].update({table_seat: apply_on_total})

    customer_name = order.find(".//CustomOrderProperties/OrderProperty[@key='CUSTOMER_NAME']")
    customer_name = customer_name.get("value") if customer_name is not None else None
    if table_seat == "table" and customer_name:
        table_loyalty_properties["CUSTOMER_NAME"] = customer_name

    from msgbusboundary import MsgBusTableService as tableService
    tableService().set_table_custom_properties(
        pos_id=pos_id,
        properties=json.dumps(table_loyalty_properties),
        table_id=table_id,
    )


def build_loyalty_properties(
    pos_id,                 # type: str
    loyalty_type_id,        # type: str
    table_id=None,          # type: Optional[str]
    order_id=None,          # type: Optional[str]
    table_seat='table',     # type: Optional[str]
    loyalty_id="",          # type: Optional[str]
):
    # type: (...) -> Tuple[Dict, Optional[str]]

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

    model = get_model(posid=pos_id)
    pos_ot = get_posot(model=model)
    if loyalty_id in ["", None]:
        loyalty_id = get_user_input_by_type(
            pos_id=pos_id,
            pos_ot=pos_ot,
            loyalty_type_id=loyalty_type_id,
            default_doc="",
            stop_action=False,
        )
    if not loyalty_id:
        return loyalty_properties, None

    try:
        loyalty_customer_info = loyalty_interactor.get_loyalty_info(loyalty_id=loyalty_id)
    except LoyaltyInteractor.LoyaltyPartnerError:
        logger.exception("Partner error")
        show_messagebox(
            posid=pos_id,
            message="$LOYALTY_PARTNER_ERROR",
        )
        return loyalty_properties, None

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

    result = parse_loyalty_customer_info(loyalty_customer_info=loyalty_customer_info)

    if not result:
        show_messagebox(
            posid=pos_id,
            message="$LOYALTY_INFORMATION_NOT_FOUND",
        )
        return loyalty_properties, None

    if order_id:
        loyalty_properties = result
        if _is_order_totaled(pos_id=pos_id):
            loyalty_properties["LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"] = []

    elif table_id:
        result["LOYALTY_ID"] = str(loyalty_id)
        result = {
            key.replace("LOYALTY_", "LOYALTY_TABLE_"): value
            for key, value in result.items()
        }
        for key, value in loyalty_properties.items():
            if key not in result:
                continue
            value.update({table_seat: result[key]})

    return loyalty_properties, loyalty_id


def _has_customer_info(
    loyalty_properties,     # type: Dict
    table_id=None,          # type: Optional[str]
    order_id=None,          # type: Optional[str]
    table_seat="table",     # type: Optional[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_TABLE_CUSTOMER_INFO", {})
        customer_info = customer_info.get(table_seat)
        return bool(customer_info)

    return False


def get_loyalty_identifier(
    customer,    # type: dict
):
    # type: (...) -> str

    loyalty_id_type = loyalty_interactor.get_loyalty_id_type()
    loyalty_id_type = "loyalty_id" if loyalty_id_type == "OTHER" else loyalty_id_type.lower()

    return customer.get(loyalty_id_type, "")


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

    if order_id:
        customer_info = loyalty_properties.get("LOYALTY_CUSTOMER_INFO", {})

    elif table_id:
        customer_info = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_INFO", {})
        customer_info = customer_info.get(table_seat, {})

    else:
        return "$LOYALTY_WELCOME_MESSAGE_CUSTOMER"

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

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

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


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

    if not (order_id or table_id):
        return

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

    if not loyalty_properties:
        return

    is_order_totaled = _is_order_totaled(pos_id=pos_id)
    customer_benefits, coupons_to_apply_on_total, _ = _get_loyalty_coupons(
        loyalty_properties=loyalty_properties,
        order_id=order_id,
        table_id=table_id,
        table_seat=table_seat,
    )

    customer_benefits = customer_benefits or []
    coupons_to_apply_on_total = coupons_to_apply_on_total or []

    available_coupons = _get_available_coupons(
        pos_id=pos_id,
        customer_benefits=customer_benefits,
        coupons_to_apply_on_total=coupons_to_apply_on_total,
        is_order_totaled=is_order_totaled,
    )

    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, options, "$SELECT_ONE_COUPON", mask="NOFILTER")

    if selected_index is None:
        return
    else:
        if apply_discount(
            pos_id=pos_id,
            selected_coupon=available_coupons[selected_index],
            is_loyalty_benefit=True,
        ):
            benefit_id = available_coupons[selected_index]["benefitId"]
            _apply_selected_benefit(
                loyalty_properties=loyalty_properties,
                pos_id=pos_id,
                benefit_id=benefit_id,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )
            raise StopAction()


def _get_loyalty_coupons(
    loyalty_properties,             # type: Dict
    order_id=None,                  # type: Optional[str]
    table_id=None,                  # type: Optional[str]
    table_seat="table",             # type: Optional[str]
    current_used_key=False,         # type: Optional[bool]
    validate_from_source=False,     # type: Optional[bool]
):
    # type: (...) -> Tuple[Dict, List, str]

    customer_benefits = None
    coupons_to_apply_on_total = None

    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"]
        current_used_key = "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"

    elif table_id:
        customer_benefits = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_BENEFITS", {})
        customer_benefits = customer_benefits.get(table_seat)
        coupons_to_apply_on_total = loyalty_properties.get("LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL", {})
        coupons_to_apply_on_total = coupons_to_apply_on_total.get(table_seat)
        current_used_key = "LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL"

    return customer_benefits, coupons_to_apply_on_total, current_used_key


def _show_applied_coupons(
    pos_id,                # type: str
    table_id=None,         # type: Optional[str]
    order_id=None,         # type: Optional[str]
    table_seat="table",    # type: Optional[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, current_used_key = _get_loyalty_coupons(
        loyalty_properties=loyalty_properties,
        order_id=order_id,
        table_id=table_id,
        table_seat=table_seat,
        current_used_key=True,
    )

    if not customer_benefits or 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

    is_order_totaled = _is_order_totaled(pos_id=pos_id)
    loyalty_property = loyalty_properties[current_used_key]
    if table_id and not is_order_totaled:
        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

        coupon_id = coupon_item.get('id')

        removed_discount = _remove_loyalty_discount(
            pos_id=pos_id,
            coupon_item=coupon_item,
            is_order_totaled=is_order_totaled,
        )
        if len(loyalty_property) > 0 and (removed_discount is True or not is_order_totaled):
            loyalty_property.remove(coupon_id)

    _apply_selected_benefit(
        loyalty_properties=loyalty_properties,
        pos_id=pos_id,
        benefit_id=None,
        table_id=table_id,
        order_id=order_id,
        table_seat=table_seat,
    )

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


def _format_coupon_options(
    pos_id,                     # type: str
    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 _apply_selected_benefit(
    loyalty_properties,  # type: Dict
    pos_id,              # type: str
    benefit_id,          # type: Optional[str]
    table_id=None,       # type: Optional[str]
    order_id=None,       # type: Optional[str]
    table_seat="table",  # type: Optional[str]
):
    # type: (...) -> None

    if order_id:
        coupons_to_apply_on_total = loyalty_properties["LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"]
        if benefit_id:
            coupons_to_apply_on_total.append(benefit_id)

        model = get_model(posid=pos_id)
        pos_ot = get_posot(model=model)
        pos_ot.setOrderCustomProperty(
            "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL",
            value=json.dumps(coupons_to_apply_on_total),
            orderid=order_id
        )

    elif table_id:
        coupons_to_apply_on_total = loyalty_properties["LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL"]
        if benefit_id:
            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]

        persist_loyalty_properties_ts(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
            only_discounts_on_total=True,
        )


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

    return 'TAB' in table_id


def _get_available_coupons(
    pos_id,                     # type: str
    customer_benefits,          # type: List
    coupons_to_apply_on_total,  # type: List
    is_order_totaled,           # type: bool
):
    # 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"))

    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: str
    order=None,      # type: Element
):
    # type: (...) -> bool

    if not order:
        model = get_model(posid=pos_id)
        order = get_current_order(model=model)
    return 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: str
    coupon_item,       # type: Dict
    is_order_totaled,  # type: bool
):
    # type: (...) -> Optional[bool]

    coupon_key = coupon_item.get('benefit_key')
    remove_discounts(
        pos_id=pos_id,
        coupon_ids=coupon_key,
    )
    return True


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: str
    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: str
    pos_ot,             # type: OrderTaker
    loyalty_type_id,    # type: str
    default_doc,        # type: str
    stop_action,        # type: bool
):
    # type: (...) -> str

    document_pin_pad = get_ask_document_pinpad(pos_id=pos_id)
    if loyalty_type_id == "DOCUMENT":
        return 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_type_id == "PHONE":
        return get_customer_phone_number(
            pos_id=pos_id,
            stop_action=stop_action,
        )

    if loyalty_type_id == "OTHER":
        loyalty_id = show_keyboard(
            posid=pos_id,
            message="$ENTER_LOYALTY_ID",
            defvalue="",
            title="",
        )
        if loyalty_id is None or loyalty_id.strip() == "":
            return ""

        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_TABLE_CUSTOMER_INFO",
        table_id=table_id,
    )
    if loyalty_info:
        loyalty_info = json.loads(loyalty_info.get("LOYALTY_TABLE_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='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 ""
