# -*- coding: utf-8 -*-
import json
from xml.etree import cElementTree as eTree

from typing import (
    Dict,
    Optional,
)

from actions.delivery import is_delivery_fee_enabled
from actions.delivery.get_delivery_fee_code import get_delivery_fee_code
from actions.functions.do_store_order import doStoreOrder
from api.order import get_order_service
from old_helper import round_half_away_from_zero
from sysactions import action, get_model, get_posot, get_operator_session, StopAction, translate_message, \
    show_confirmation, get_current_order

from bustoken import TK_REMOTE_ORDER_SEND_ORDER_TO_PRODUCTION
from msgbus import FM_PARAM, TK_SYS_ACK
from sysactions import show_messagebox

from mw_helper import format_currency

from custom_sysactions import (
    block_action_if_order_items_not_resolved,
    fix_current_order_in_model,
    validate_order_state,
)
from model.customexception import (
    OrderNotFoundException,
    SendOrderToProductionError,
)
from .. import logger
from .. import mb_context
from modelcommon.remoteorder import DeliveryDiscounts


@action
def produce_delivery_order(
    pos_id,                         # type: str
    order_id,                       # type: str
    warn_inconsistencies='false',   # type: Optional[str]
):
    # type: (...) -> None

    produce_order(
        pos_id=pos_id,
        order_id=order_id,
        warn_inconsistencies=warn_inconsistencies,
    )


@action
@validate_order_state
@block_action_if_order_items_not_resolved
def produce_current_delivery_order(
    pos_id,                         # type: str
    warn_inconsistencies='false',   # type: Optional[str]
):
    # type: (...) -> None

    produce_order(
        pos_id=pos_id,
        order_id=None,
        warn_inconsistencies=warn_inconsistencies,
    )


def produce_order(
    pos_id,                         # type: str
    order_id=None,                  # type: Optional[str]
    warn_inconsistencies='false',   # type: Optional[str]
):
    # type: (...) -> None

    model = get_model(pos_id)
    pos_ot = get_posot(model)
    session = get_operator_session(model)

    order_service = get_order_service(pos_id=pos_id)
    if order_id is None:
        order_in_current_pos = True
        model_order = model.find(".//Order")
        order_id = model_order.get("orderId")
    else:
        order_in_current_pos = False
        order_service.recall_order(order_id=order_id)

    pos_ot.clearDiscount(1)

    if not _validate_manual_order(model, pos_ot, pos_id, order_id, warn_inconsistencies.lower() != 'false'):
        return

    try:
        try:
            doStoreOrder(
                pos_id=pos_id,
                show_popup=False,
            )
        except OrderNotFoundException as ex:
            fixed = fix_current_order_in_model(
                pos_id=pos_id,
                model=model,
                order_taker=pos_ot,
                order_id=order_id,
            )
            if not fixed:
                raise ex

        send_remote_order_to_production(order_id=order_id)
        show_messagebox(pos_id, "$ORDER_PRODUCED")
    except Exception as ex:
        logger.exception("Error producing delivery order")
        show_messagebox(pos_id, message=ex.message)

        if order_in_current_pos:
            order_service.recall_order(
                order_id=order_id,
                session_id=session,
            )

        raise StopAction()


def send_remote_order_to_production(
    order_id,       # type: str
):
    # type: (...) -> None

    msg = mb_context.MB_EasySendMessage(
        "RemoteOrder",
        TK_REMOTE_ORDER_SEND_ORDER_TO_PRODUCTION,
        FM_PARAM,
        order_id
    )
    if msg.token != TK_SYS_ACK:
        raise SendOrderToProductionError("$ERROR_SENDING_REMOTE_ORDER_TO_PRODUCTION|{}".format(msg.data))


def _validate_manual_order(model, pos_ot, pos_id, order_id, warn_inconsistencies):
    try:
        order_picture = eTree.XML(pos_ot.orderPicture(pos_id, order_id))
        manual_order = order_picture.find("Order") if order_picture.find("Order") else order_picture
        remote_order_json_element = order_picture.find(".//OrderProperty[@key='REMOTE_ORDER_JSON']")
        remote_order_json_text = None
        if remote_order_json_element is not None:
            remote_order_json_text = remote_order_json_element.get("value", None)
        if not remote_order_json_text or not warn_inconsistencies:
            return True

        remote_json = json.loads(remote_order_json_text)

        order_items_quantity = _get_order_items_quantity(manual_order)
        order_total_value = _get_order_total_value(manual_order)
        json_items_quantity = _get_json_items_quantity(remote_json)
        json_total_value = _get_json_total_value(remote_json)

        quantity_is_different, quantity_diff = _items_quantity_is_different(order_items_quantity, json_items_quantity)
        value_is_different, value_diff = _value_is_different(order_total_value, json_total_value)

        if quantity_is_different or value_is_different:
            _confirm_order_difference(model, pos_id, remote_json)
            if quantity_is_different:
                _validate_quantity_difference(model, pos_id, order_items_quantity, json_items_quantity)
            if value_is_different:
                _validate_value_difference(model, pos_id, value_diff, order_total_value, json_total_value)

    except StopAction:
        return False
    except (Exception,):
        logger.exception("Error validating manual order")
        return False

    return True


def _validate_value_difference(model, pos_id, value_diff, order_total_value, json_total_value):
    message = "$CONFIRM_MANUAL_ORDER_DISCOUNT" if value_diff > 0 else "$CONFIRM_MANUAL_ORDER_ADDITION"
    message = message + "|{0}|{1}|{2}".format(
        format_currency(
            model=model,
            value=order_total_value,
        ),
        format_currency(
            model=model,
            value=json_total_value,
        ),
        format_currency(
            model=model,
            value=abs(value_diff),
        )
    )
    if not show_confirmation(pos_id, message):
        raise StopAction()

    _apply_difference_discount(pos_id, value_diff)


def _validate_quantity_difference(model, pos_id, order_items_quantity, json_items_quantity):
    message = "$CONFIRM_MANUAL_ORDER_ITEM_QUANTITY_DIFF"
    message = message + "|{0}|{1}".format(order_items_quantity, json_items_quantity)
    if not show_confirmation(pos_id, message):
        raise StopAction()


def _apply_difference_discount(pos_id, value_diff):
    from actions.discount.apply_discount import apply_amount_discount

    model = get_model(pos_id)
    pos_ot = get_posot(model)
    order = get_current_order(model)
    apply_amount_discount(pos_id, pos_ot, order, discount_type=1, discount_value=float(value_diff))


def _confirm_order_difference(model, pos_id, remote_json):
    partner_order_id = _get_json_order_id(remote_json)
    message = translate_message(model, "DELIVERY_RECALL_WARNING")
    message = message.format(
        partner_order_id,
        remote_json.get("partner", "").upper(),
        str(_get_json_items_quantity(remote_json)),
        format_currency(
            model=model,
            value=_get_json_total_value(remote_json),
        )
    )
    if not show_confirmation(pos_id, message):
        raise StopAction()


def _items_quantity_is_different(order_items_quantity, json_items_quantity):
    is_different = not (order_items_quantity == json_items_quantity)
    value_diff = order_items_quantity - json_items_quantity

    return is_different, value_diff


def _value_is_different(order_total_value, json_total_value):
    round_order_value = round_half_away_from_zero(order_total_value, 2)
    round_json_value = round_half_away_from_zero(json_total_value, 2)
    is_different = not (round_order_value == round_json_value)
    value_diff = round_half_away_from_zero(order_total_value - json_total_value, 2)

    return is_different, value_diff


def _get_order_total_value(manual_order):
    manual_order_total = float(manual_order.get("totalAfterDiscount", 0)) + float(manual_order.get("taxTotal", 0))
    return manual_order_total


def _get_json_total_value(
    remote_json,    # type:Dict
):
    # type: (...) -> float

    vouchers = remote_json.get("vouchers")
    delivery_discounts = DeliveryDiscounts(logger=logger)
    json_discounts = delivery_discounts.get_discount_amount_by_type(
        vouchers=vouchers,
        discount_type="store",
    )
    json_total_value = float(remote_json.get("subTotal", 0)) - json_discounts

    delivery_fee_value = 0
    if is_delivery_fee_enabled():
        json_delivery_fee_discount = delivery_discounts.get_delivery_fee_discounts(vouchers=vouchers)
        delivery_fee_value = float(remote_json.get("deliveryFee", 0)) - json_delivery_fee_discount

    return json_total_value + delivery_fee_value


def _get_order_items_quantity(manual_order):
    sale_lines = manual_order.findall(".//SaleLine[@level='0']")
    delivery_fee_code = get_delivery_fee_code()

    return sum(float(c.get("qty", "0")) for c in _get_valid_sale_lines(sale_lines, delivery_fee_code))


def _get_valid_sale_lines(sale_lines, fee_code):
    return [line for line in sale_lines if (float(line.get("qty", "0")) > 0 and line.get("partCode", "") != fee_code)]


def _get_json_items_quantity(remote_json):
    valid_items = [line for line in remote_json.get("items", []) if (float(line.get("quantity", "0")) > 0)]

    return sum(float(c.get("quantity", "0")) for c in valid_items)


def _get_json_order_id(remote_json):
    return remote_json.get("shortReference", "")
