# -*- coding: utf-8 -*-

import json
import logging

from xml.etree.ElementTree import Element

from application.interactor import NDiscountInteractor, OrderInteractor
from msgbus import TK_SYS_ACK
from mw_helper import show_message_dialog, show_check_list_box_dialog, show_filtered_list_box_dialog
from posot import OrderTaker
from sysactions import get_model, get_current_order, format_amount, show_messagebox, get_posot, translate_message, \
    StopAction
from xml.etree import cElementTree as eTree

from typing import Dict, Optional, List

logger = logging.getLogger("PosActions")


def display_coupons(pos_id, table_id=None):
    model = get_model(pos_id)
    pos_ot = get_posot(model)
    order = get_current_order(model)
    if order is None:
        return

    try:
        benefits = get_benefits_from_order(pos_ot, order)
        if benefits is None or len(benefits) == 0:
            show_messagebox(pos_id, "$ORDER_HAS_NO_APPLIED_DISCOUNT")
            return

        applied_coupons = _get_applied_coupons_from_benefits(model, order, benefits, table_id)
        selected_indexes = _select_coupon(pos_id, applied_coupons)
        if not len(selected_indexes):
            return

        coupon_ids = []
        for selected_index in selected_indexes:
            index = int(selected_index)
            if index == 0:
                continue

            coupon = [x for x in applied_coupons if x["index"] == index]
            if not coupon:
                continue

            disable = coupon[0]["name"].split("##")[2]
            if not eval(disable):
                coupon_ids.append(coupon[0]["id"])

        msg = NDiscountInteractor.remove_benefits(pos_id, coupon_ids)
        if msg.token != TK_SYS_ACK:
            show_message_dialog(pos_id, "$ERROR", msg.data)
            raise StopAction()

        if table_id is not None:
            from table_actions import process_discount_in_table, clean_table_payments, create_table_modified_event

            table_picture = clean_table_payments(pos_id, table_id, delete_order_tender=False)
            create_table_modified_event(pos_id, table_picture)

            process_discount_in_table(pos_id, order, table_id)

        return True

    except StopAction:
        raise
    except Exception as ex:
        show_message_dialog(pos_id, "$ERROR", ex.message)
        logger.exception("Error removing discount")
        return False


def get_benefits_from_order(pos_ot, order, custom_properties=None):
    if not order:
        return []

    if not custom_properties:
        custom_properties = eTree.XML(pos_ot.getOrderCustomProperties(orderid=order.get("orderId")))

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

    benefit_list_json = json.loads(benefit_list_custom_property.get("value"))
    benefit_json = benefit_list_json.get("benefitList")
    benefit_list = json.loads(benefit_json)
    benefit_list = [b for b in benefit_list if b['benefit_applied']]

    return benefit_list


def _get_applied_coupons_from_benefits(model, order, benefits, table_id):
    discounts = get_lines_discount_amounts_by_ids(order, table_id, None)
    all_coupons = translate_message(model, "ALL_COUPONS")
    applied_coupons = []
    total_amount = 0
    benefit_index = 1

    for benefit in benefits:
        benefit_applied = benefit.get("benefit_applied", False)
        if not benefit_applied:
            continue

        benefit_detail = benefit["benefit"]
        amount = 0
        if "used_discount_ids" in benefit:
            for d in benefit["used_discount_ids"]:
                amount += discounts[str(d)] if str(d) in discounts.keys() else 0
        if "order_tenders_ids" in benefit:
            for t in benefit["order_tenders_ids"]:
                amount += _get_tender_discount(t, discounts)
        if benefit_detail:
            added_sale_lines = len(benefit.get("added_sale_lines", []))
            if added_sale_lines and amount == 0:
                continue

            benefit_key = benefit["benefit_key"]
            description = (benefit_detail.get("userDescription") or benefit_detail.get("id") or "").encode("UTF-8")
            benefit_added_in_progress = benefit["order_state"] == "IN_PROGRESS"
            disable = benefit_added_in_progress or added_sale_lines > 0
            name = "{}##{}##{}".format(description, format_amount(model, amount, False), disable)
            data = {"index": benefit_index, "id": benefit_key, "name": name}
            applied_coupons.append(data)

        benefit_index += 1
        total_amount += amount

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


def _select_coupon(pos_id, coupons):
    options = ["{0}".format(c["name"]) for c in coupons]
    return show_check_list_box_dialog(pos_id, options, "$SELECT_ONE_COUPON", buttons="$REMOVE|$CANCEL")


def get_lines_discount_amounts_by_ids(order, table_id, discounts):
    if discounts is None:
        discounts = {}
    sale_lines = order.findall(".//SaleLine")
    for sale_line in sale_lines:
        if float(sale_line.get("qty") or 0) == 0:
            continue

        discounts_applied = sale_line.get("discountsApplied")
        if discounts_applied:
            _add_discounts_applied(discounts_applied, discounts)

    if table_id:
        discounts = _add_tenders_amounts_by_ids_from_table(int(order.get("posId", 0)), table_id, discounts)
    else:
        discounts = _add_tenders_amounts_by_ids_from_order(order, discounts)
    return discounts


def _add_tenders_amounts_by_ids_from_table(pos_id, table_id, discounts):
    # type (int, str, Dict) -> Dict

    from tablemgrapi import get_pos_ts
    model = get_model(pos_id)
    pos_ts = get_pos_ts(model)
    table_picture = pos_ts.getTablePicture(table_id)
    table_picture_xml = eTree.XML(table_picture)

    tender_lines = table_picture_xml.findall(".//ServiceTenders/ServiceTender")
    for tender_line in tender_lines:
        tender_id = tender_line.get("orderTenderId", None)
        _add_tender_discount(tender_line, discounts, tender_id)
    return discounts


def _add_tenders_amounts_by_ids_from_order(order, discounts):
    # type: (Element, Dict) -> Dict

    tender_lines = order.findall(".//Tender")
    for tender_line in tender_lines:
        tender_id = tender_line.get("tenderId", None)
        _add_tender_discount(tender_line, discounts, tender_id)
    return discounts


def _add_discounts_applied(discounts_applied, discounts):
    for discount in discounts_applied.split(','):
        discount_id, amount = discount.split(':')
        if discount_id != '2':
            if discount_id in discounts.keys():
                discounts[discount_id] += float(amount)
            else:
                discounts[discount_id] = float(amount)


def _add_tender_discount(tender_line, discounts, tender_id):
    # type: (Element, Dict, str) -> None

    if tender_id is None:
        return

    tender = "tender{}".format(tender_id)
    if tender not in discounts:
        discounts[tender] = 0

    discounts[tender] = float(tender_line.get("tenderAmount", None) or 0)


def _get_tender_discount(tender_id, discounts):
    tender_id = "tender{}".format(tender_id)
    if tender_id in discounts.keys():
        return discounts[tender_id]
    return 0


def get_all_order_tenders_ids(
        pos_ot,         # type: OrderTaker
        model,          # type: Element
        order,          # type: Element
        table_id=None   # type: Optional[str]
):
    # type: (...) -> (List[int], List[int])

    benefits = get_benefits_from_order(pos_ot, order) or []
    benefit_tenders_ids = []
    for b in benefits:
        for t in b["order_tenders_ids"]:
            benefit_tenders_ids.append(int(t))

    if table_id:
        all_tenders_ids = _get_table_service_tenders_by_order_tender(model, table_id)
        not_benefit_tenders_ids = [all_tenders_ids[k] for k in all_tenders_ids.keys() if k not in benefit_tenders_ids]
    else:
        all_tenders_ids = []
        tender_lines = order.findall(".//Tender")
        for tender_line in tender_lines:
            all_tenders_ids.append(int(tender_line.get("tenderId", -1)))

        not_benefit_tenders_ids = [k for k in all_tenders_ids if k not in benefit_tenders_ids]

    return not_benefit_tenders_ids, benefit_tenders_ids


def _get_table_service_tenders_by_order_tender(model, table_id):
    from tablemgrapi import get_pos_ts

    tenders_ids = {}
    pos_ts = get_pos_ts(model)
    table_picture = pos_ts.getTablePicture(table_id)
    table_picture_xml = eTree.XML(table_picture)
    service_tenders = table_picture_xml.findall(".//ServiceTenders/ServiceTender")
    for service_tender in service_tenders:
        tenders_ids[int(service_tender.get("orderTenderId") or -1)] = int(service_tender.get("serviceTenderId") or -1)

    return tenders_ids


def display_coupons_from_report(pos_id, initial_date, end_date, operator_id):
    # type: (str, str, str, Optional[str]) -> List

    try:
        if initial_date is None or end_date is None:
            raise StopAction()

        benefits = OrderInteractor(pos_id).get_orders_benefit(initial_date, end_date, operator_id)
        if benefits is None or len(benefits) == 1:
            show_messagebox(pos_id, "$ORDER_HAS_NO_APPLIED_DISCOUNT")
            raise StopAction()

        applied_benefits = [benefit["name"] for benefit in benefits]
        sorted_applied_benefits = [applied_benefits[0]] + sorted(list(dict.fromkeys(applied_benefits[1:])))
        options = ["{0}##True".format(benefit) for benefit in sorted_applied_benefits]
        selected_index = show_filtered_list_box_dialog(pos_id, options, "$BENEFIT_REPORT", mask="NOFILTER")

        if selected_index is None:
            raise StopAction()

        chosen_benefit = [x for x in benefits if x["name"] == sorted_applied_benefits[selected_index]]
        return chosen_benefit
    except StopAction:
        raise
    except Exception as ex:
        show_message_dialog(pos_id, "$ERROR", ex.message)
        logger.exception("Error display coupons from report")
        raise StopAction()

