import json
import logging

from msgbus import (
    FM_PARAM,
    TK_SYS_ACK, MBException,
)
from typing import (
    Dict,
    List,
    Optional,
    Tuple,
)

from mw_helper import (
    show_check_list_box_dialog,
    show_filtered_list_box_dialog,
    show_message_dialog,
    show_message_options_dialog,
    show_loyalty_dialog,
)
from sysactions import (
    action,
    get_model,
    get_posot,
    send_message,
    show_messagebox,
    translate_message,
)

from bustoken import TK_LOYALTY_GET_LOYALTY_ID

from actions.customer import get_customer_doc
from actions.loyalty.model import LoyaltyButtonEnum
from utilfunctions import (
    get_custom_property_value,
    parse_loyalty_customer_info,
    update_custom_properties,
)


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

        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,
            )
        else:
            has_customer_info = False

        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,
            )
            cpf_button = "$REMOVE_CPF"
            is_disabled = ""
        else:
            welcome_message = "$INSERT_CUSTOMER_DOC"
            cpf_button = "$INFORM_CPF"
            is_disabled = '#DISABLED'

        buttons = "{}|$AVAILABLE_COUPONS{}|$APPLIED_COUPONS{}|$CLOSE".format(
            cpf_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

        if resp == LoyaltyButtonEnum.UPDATE_CPF:
            if has_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,
                )

        elif resp == LoyaltyButtonEnum.AVAILABLE_COUPONS:
            if has_customer_info:
                _show_available_coupons(
                    pos_id=pos_id,
                    table_id=table_id,
                    order_id=order_id,
                    table_seat=table_seat,
                )

        elif resp == LoyaltyButtonEnum.APPLIED_COUPONS and has_customer_info:
            _show_applied_coupons(
                pos_id=pos_id,
                table_id=table_id,
                order_id=order_id,
                table_seat=table_seat,
            )

    return True


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 get_loyalty_info(
    customer_doc,   # type: str
):
    # type: (...) -> Optional[str]

    if not customer_doc:
        return

    try:
        ret = send_message(
            dest_name="Loyalty",
            token=TK_LOYALTY_GET_LOYALTY_ID,
            format=FM_PARAM,
            data=customer_doc,
        )
        if ret.token == TK_SYS_ACK:
            return ret.data
    except MBException as ex:
        if "SE_NOTFOUND" in ex.message:
            return None

        raise ex


def _set_loyalty_properties(
    pos_id,          # type: str
    table_id,        # type: str
    order_id,        # type: str
    table_seat,      # type: str
):
    # type: (...) -> None

    if order_id not in [None, ""]:
        from posactions import doSetCustomerDocument
        doSetCustomerDocument(
            pos_id=pos_id,
            stop_action=False,
        )

    elif table_id is not None:
        loyalty_properties = _build_loyalty_properties(
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
        )
        _persist_loyalty_properties_ts(
            loyalty_properties=loyalty_properties,
            pos_id=pos_id,
            table_id=table_id,
            table_seat=table_seat,
        )


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

    customer_info = {}
    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 "{}"
        tableService().set_table_custom_property(
            pos_id=pos_id,
            prop="LOYALTY_TABLE_CUSTOMER_INFO",
            prop_value=customer_info_value,
            table_id=table_id,
        )

    if not only_discounts_on_total:
        benefits = loyalty_properties.get("LOYALTY_TABLE_CUSTOMER_BENEFITS")
        benefits_value = json.dumps(benefits) if benefits else "{}"
        tableService().set_table_custom_property(
            pos_id=pos_id,
            prop="LOYALTY_TABLE_CUSTOMER_BENEFITS",
            prop_value=benefits_value,
            table_id=table_id,
        )

    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 "{}"
    tableService().set_table_custom_property(
        pos_id=pos_id,
        prop="LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL",
        prop_value=coupons_on_total_value,
        table_id=table_id,
    )

    if not only_discounts_on_total and (table_seat == "table" or _is_tab(table_id=table_id)):
        table_customer = customer_info.get("table", {})
        if isinstance(table_customer, basestring):
            table_customer = json.loads(table_customer)
        customer_info = table_customer.get("customerInfo", {})
        customer = customer_info.get("customer", {})
        document = customer.get("document", "")
        name = customer.get("name", "")
        tableService().set_table_custom_property(
            pos_id=pos_id,
            prop="CUSTOMER_DOC",
            prop_value=document,
            table_id=table_id,
        )
        tableService().set_table_custom_property(
            pos_id=pos_id,
            prop="CUSTOMER_NAME",
            prop_value=name,
            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]

    customer_info = get_custom_property_value(
        pos_id=pos_id,
        custom_property="LOYALTY_CUSTOMER_INFO",
        order_id=order_id,
    )
    benefits = get_custom_property_value(
        pos_id=pos_id,
        custom_property="LOYALTY_CUSTOMER_BENEFITS",
        order_id=order_id,
    )
    coupons_on_total = get_custom_property_value(
        pos_id=pos_id,
        custom_property="LOYALTY_COUPONS_TO_APPLY_ON_TOTAL",
        order_id=order_id,
    )
    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 _get_loyalty_properties_for_ts(
    pos_id,     # type: str
    table_id,   # type: str
):
    # type: (...) -> Optional[Dict]

    from msgbusboundary import MsgBusTableService as tableService

    customer_info = tableService().get_table_custom_property(
        pos_id=pos_id,
        prop="LOYALTY_TABLE_CUSTOMER_INFO",
        table_id=table_id
    )
    benefits = tableService().get_table_custom_property(
        pos_id=pos_id,
        prop="LOYALTY_TABLE_CUSTOMER_BENEFITS",
        table_id=table_id
    )
    coupons_on_total = tableService().get_table_custom_property(
        pos_id=pos_id,
        prop="LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL",
        table_id=table_id
    )

    if customer_info:
        customer_info = json.loads(customer_info["LOYALTY_TABLE_CUSTOMER_INFO"])
        _parse_object_children(dictionary=customer_info)

    if benefits:
        benefits = json.loads(benefits["LOYALTY_TABLE_CUSTOMER_BENEFITS"])
        _parse_object_children(dictionary=benefits)

    if coupons_on_total:
        coupons_on_total = json.loads(coupons_on_total["LOYALTY_TABLE_COUPONS_TO_APPLY_ON_TOTAL"])
        _parse_object_children(dictionary=coupons_on_total)

    loyalty_properties = {
        "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 _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

    if order_id not in [None, ""]:
        model = get_model(posid=pos_id)
        pos_ot = get_posot(model=model)
        order_properties_dict = update_custom_properties(
            customer_doc="clear",
            customer_name="clear",
            loyalty_customer_info="clear",
        )

        for prop in order_properties_dict.keys():
            if order_properties_dict[prop] in [None, ""]:
                pos_ot.setOrderCustomProperty(key=prop)
                del order_properties_dict[prop]

        if len(order_properties_dict) > 0:
            pos_ot.setOrderCustomProperties(properties=order_properties_dict)

    elif table_id is not 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 _build_loyalty_properties(
    pos_id,                 # type: str
    table_id=None,          # type: Optional[str]
    order_id=None,          # type: Optional[str]
    table_seat='table',     # type: Optional[str]
):
    # type: (...) -> Dict

    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)
    document = get_customer_doc(
        pos_id=pos_id,
        pos_ot=pos_ot,
        stop_action=False,
    )
    if not document:
        return loyalty_properties

    loyalty_customer_info = get_loyalty_info(customer_doc=document)
    result = parse_loyalty_customer_info(loyalty_customer_info=loyalty_customer_info)

    if not result:
        return loyalty_properties

    if order_id:
        loyalty_properties = result

    elif table_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


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_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 = customer.get("document", "")

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

    message = "$WELCOME_CLIENT_LOYALTY_INFO|{}|{}|{}".format(
        customer_name,
        customer_doc,
        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

    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 = [
        {
            "coupon": benefit,
            "name": benefit.get("userDescription", ""),
            "benefitId": benefit.get("id"),
        }
        for index, benefit in enumerate(customer_benefits)
        if benefit.get("id") not in coupons_to_apply_on_total
    ]

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

    options = [
        "{} - {}".format(
            index + 1,
            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:
        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,
        )


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]
):
    # type: (...) -> Tuple[Dict, List, str]

    customer_benefits = None
    coupons_to_apply_on_total = None

    if order_id:
        customer_benefits = loyalty_properties["LOYALTY_CUSTOMER_BENEFITS"]
        coupons_to_apply_on_total = loyalty_properties["LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"]
        current_used_key = "LOYALTY_COUPONS_TO_APPLY_ON_TOTAL"

    elif table_id:
        customer_benefits = loyalty_properties["LOYALTY_TABLE_CUSTOMER_BENEFITS"]
        customer_benefits = customer_benefits.get(table_seat)
        coupons_to_apply_on_total = loyalty_properties["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]
    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

    if len(selected_indexes) == len(coupon_options):
        if order_id:
            loyalty_properties[current_used_key] = []
        elif table_id:
            loyalty_properties[current_used_key][table_seat] = []

    else:
        loyalty_property = []
        if order_id:
            loyalty_property = loyalty_properties[current_used_key]
        elif table_id:
            loyalty_property = loyalty_properties[current_used_key][table_seat]

        for selected_index in selected_indexes:
            index = int(selected_index)
            coupon_item = [x for x in coupon_options if x["index"] == index]
            coupon_id = coupon_item[0]['id']

            if index == 0:
                continue
            if not coupon_item:
                continue

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


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

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

    for coupon in coupon_options:
        benefit_id = coupon.get('id')

        if benefit_id not in coupons_to_apply_on_total:
            continue

        disable = False
        description = (coupon.get("userDescription") or coupon.get("id") or "").encode("UTF-8")
        name = "{}##{}##{}".format(description, 'null', disable)
        data = {"index": benefit_index, "id": benefit_id, "name": name}
        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
