import logging

import sysactions

from xml.etree import cElementTree as eTree
from typing import (
    Optional,
    Dict,
    List,
)

from mw_helper import show_filtered_list_box_dialog, show_numpad_dialog, show_message_dialog
from sysactions import show_messagebox, get_current_order, StopAction, get_model, get_posot, show_keyboard

from actions.models import PaymentType
from actions.coupon.apply_coupon import apply_coupon
from actions.discount.get_discount import get_visible_discounts
from actions.util import (
    get_authorization,
    get_user_level,
)
from application.interactor import NDiscountInteractor
from manager.reports import justification_report
from model.customexception import ErrorAddingBenefits, DiscountMinLevelException, ErrorApplyingDiscount

logger = logging.getLogger("PosActions")


def apply_discount(
        pos_id,                     # type: str
        selected_coupon=None,       # type: Optional[Dict]
        amount=0.0,                 # type: Optional[float]
        user_auth_level=-1,         # type: Optional[int]
        user_auth_name=None,        # type: Optional[str]
        user_auth_id=None,          # type: Optional[str]
        justification=None,         # type: Optional[str]
):
    # type: (...) -> bool

    try:
        coupons = get_visible_discounts(pos_id)
        selected_coupon = _select_coupon(pos_id, selected_coupon, coupons)
        if not selected_coupon:
            return False

        coupon = selected_coupon.get("coupon", {})
        model = get_model(pos_id)

        if user_auth_id is None:
            operator = model.find("Operator")
            user_auth_id = operator.get("id")

        try:
            if coupon.get("requestJustification", False):
                if justification is None:
                    justification = _get_justification_discount(pos_id=pos_id)

                coupon['justification'] = justification
                coupon['appliedBy'] = user_auth_id

            if coupon.get("askValue", False) and not amount:
                coupon["index"] = _get_discount_type(coupon)
                amount = amount or _get_discount_amount(coupon, pos_id)
                coupon = set_coupon_amount(
                    coupon=coupon,
                    amount=amount,
                )

            _add_benefit(
                pos_id=pos_id,
                coupon=coupon,
                user_auth_level=user_auth_level,
                benefit_id=selected_coupon.get("benefit_id", ""),
                user_auth=user_auth_name,
                justification=justification,
                amount=amount,
            )
            return True
        except DiscountMinLevelException as _:
            show_messagebox(pos_id, "$NDISCOUNT_ERROR_USER_MIN_LEVEL")
            user_was_authorized, user_auth = get_authorization(
                pos_id=pos_id,
                min_level=coupon.get("conditions").get("userMinLevel", 0),
                model=model
            )
            if user_was_authorized:
                user_xml_str = sysactions.get_user_information(user_auth)
                user_element = eTree.XML(user_xml_str).find("user")
                user_auth_level = int(user_element.attrib["Level"])
                user_auth_id = str(user_element.attrib["UserId"])
                coupon['authorizedBy'] = user_auth_id

                apply_discount(
                    pos_id=pos_id,
                    selected_coupon=selected_coupon,
                    amount=amount,
                    user_auth_level=user_auth_level,
                    user_auth_name=user_element.attrib["UserName"],
                    user_auth_id=user_auth_id,
                    justification=justification
                )
                return True

    except (ErrorAddingBenefits, ErrorApplyingDiscount) as ex:
        logger.warn("Error applying discount ${}".format(ex.message))
        show_message_dialog(pos_id, "$ERROR", "${}".format(ex.message))
    except Exception as ex:
        logger.exception("Error applying discount")
        message = "FATAL_ERROR"
        if hasattr(ex, 'message'):
            message = "${}".format(message)
        show_message_dialog(pos_id, "$ERROR", message)

    return False


def _select_coupon(
    pos_id,             # type: str
    selected_coupon,    # type: Optional[Dict]
    coupons,            # type: List
):
    # type: (...) -> Optional[Dict]

    if not selected_coupon:
        if len(coupons) == 0:
            show_messagebox(pos_id, "$DISCOUNT_NOT_FOUND", icon="warning")
            return None

        selected_index = _select_discount(pos_id, coupons)
        if selected_index is None:
            return None

        selected_coupon = filter(lambda c: c["index"] == selected_index + 1, coupons)
        if not selected_coupon:
            return None

        selected_coupon = selected_coupon[0]

    return selected_coupon


def _get_discount_amount(coupon, pos_id):
    model = get_model(pos_id)
    order = get_current_order(model)
    amount, percentage = _ask_discount_amount(pos_id, order, coupon=coupon)
    if percentage is not None:
        amount = percentage

    return amount


def apply_amount_discount(pos_id, pos_ot, order, coupon=None, discount_type=None, discount_value=None):
    amount, _ = _ask_discount_amount(pos_id, order, coupon, discount_type, discount_value)
    pos_ot.applyDiscount(1, amount, forcebyitem=1)


def _ask_discount_amount(pos_id, order, coupon=None, discount_type=None, discount_value=None):
    if not discount_type:
        discount_type = coupon['index']
        title = "$DISCOUNT_VALUE_TITLE" if discount_type == PaymentType.AMOUNT.value else "$DISCOUNT_PERCENTAGE_TITLE"
        mask = "CURRENCY" if discount_type == PaymentType.AMOUNT.value else "NUMBER"
        if not discount_value:
            discount_value = show_numpad_dialog(pos_id, title, "", mask)
            if discount_value in [None, '']:
                raise StopAction()

    if discount_type == PaymentType.AMOUNT.value:
        return discount_value, None

    amount = float(order.get("totalGross"))

    return (amount * discount_value) / 100, discount_value


def _select_discount(pos_id, coupons):
    options = ["{0} - {1}".format(coupon["index"], coupon["name"].encode("UTF-8")) for coupon in coupons]
    return show_filtered_list_box_dialog(pos_id, options, "$SELECT_ONE_COUPON", mask="NOFILTER")


def _add_benefit(
    pos_id,             # type: str
    coupon,             # type: Dict
    user_auth_level,    # type: int
    benefit_id,         # type: str
    user_auth,          # type: str
    justification,      # type: str
    amount,             # type: float
):
    # type: (...) -> None

    authorization_level = int(coupon.get("conditions", {}).get("authorizationLevel", 0))
    if _get_discount_authorization(
        pos_id=pos_id,
        coupon=coupon,
        authorization_level=authorization_level,
    ) is False:
        raise StopAction()

    if justification or amount:
        NDiscountInteractor.add_benefit(
            pos_id=pos_id,
            benefit_id="",
            benefit=coupon,
            user_auth_level=user_auth_level,
        )
        if justification and coupon.get("printJustifyCoupon", False):
            justification_report(
                pos_id=pos_id,
                justification=justification,
                discount_description=coupon.get('userDescription', ""),
                authorized_by=user_auth,
            )
        return

    return apply_coupon(
        pos_id=pos_id,
        table_id=None,
        coupon_code=benefit_id,
        user_auth_level=user_auth_level,
        raise_error=True,
    )


def _get_object_json(json_object, key, value):
    return [obj for obj in json_object if obj[key] == value][0]


def set_coupon_amount(coupon, amount):
    if coupon.get("actions", False) and coupon.get("actions").get("0", False):
        action = coupon.get("actions").get("0")
        value_actions = ["discountAmountOnTotal", "discountPercentageOnTotal"]
        group_actions = ["discountPercentageByGroupsOnTotal", "discountAmountByGroupsOnTotal"]
        if any(x for x in value_actions if x in action.keys()):
            action_name = action.keys()[0]
            action[action_name] = amount
            return coupon
        elif any(x for x in group_actions if x in action.keys()):
            action.values()[0][0]["amount"] = amount
            return coupon

    raise StopAction()


def _get_discount_type(coupon):
    if coupon.get("actions", False) and coupon.get("actions").get("0", False):
        action = coupon.get("actions").get("0")
        amount_actions = ["discountAmountOnTotal", "discountAmountByGroupsOnTotal"]
        percentage_actions = ["discountPercentageOnTotal", "discountPercentageByGroupsOnTotal"]
        if any(x for x in amount_actions if x in action.keys()):
            return PaymentType.AMOUNT.value
        elif any(x for x in percentage_actions if x in action.keys()):
            return PaymentType.PERCENTAGE.value

    raise StopAction()


def _get_justification_discount(pos_id):
    # type: (str) -> str

    while True:
        justification = show_keyboard(pos_id, "$JUSTIFICATION_DISCOUNT", '', timeout=1800000, buttons="$OK")
        if justification is None:
            raise StopAction()

        justification = justification.strip()
        if len(justification) < 5:
            show_messagebox(
                posid=pos_id,
                message="$MINIMUM_5_CHARACTERS_REQUIRED",
            )
            continue

        break

    return justification


def _get_discount_authorization(
    pos_id,                 # type: str
    coupon,                 # type: dict
    authorization_level,    # type: int
):
    # type: (...) -> Optional[dict, bool]

    model = get_model(posid=pos_id)
    operator = model.find("./Operator")
    user_id = operator.get("id")
    current_user_level = get_user_level(user_id=user_id)

    if current_user_level >= authorization_level:
        coupon['authorized_by'] = user_id
        return coupon

    user_was_authorized, user_auth = get_authorization(
        pos_id=pos_id,
        min_level=authorization_level,
        model=model,
    )
    authorized_user_level = get_user_level(user_id=user_auth)
    if user_was_authorized and authorized_user_level >= authorization_level:
        coupon['authorized_by'] = user_auth
        return coupon

    show_messagebox(
        posid=pos_id,
        message="$NDISCOUNT_ERROR_USER_MIN_LEVEL",
    )
    return False
