import base64

from datetime import datetime, timedelta
from time import sleep

from typing import Dict
from xml.etree import cElementTree as eTree


from actions.eft import cancel_eft_transactions
from old_helper import convert_from_utf_to_localtime

import sysactions
from custom_sysactions import user_authorized_to_execute_action
from utils import get_void_reason_id, void_order, void_fiscal_order
from .. import logger
from ..util.get_menu_user_logged import get_menu_manager_authenticated_user

VOID_ATTEMPTS = 3


@sysactions.action
@user_authorized_to_execute_action
def do_void_order(
    pos_id,
    void_reason=None,
    fiscal_void='false',
    order_id="",
    show_cancel_message=True,
    is_mobile=False
):
    # type: (...) -> bool

    try:

        model = sysactions.get_model(pos_id)
        originator_pos_id = pos_id
        is_mobile = str(is_mobile).lower() == 'true'
        canceled_by = None

        if fiscal_void.lower() == 'true':
            order_id, originator_pos_id = _void_fiscal_order(pos_id, model)
            canceled_by = _get_canceled_by(
                pos_id,
                model,
            )
        else:
            sysactions.check_current_order(pos_id, model)

        void_reason = get_void_reason_id(
            pos_id=pos_id,
            selected_reason=void_reason,
            operator=canceled_by
        )
        if not void_reason:
            return True

        _void_order(
            order_id,
            originator_pos_id,
            void_reason,
        )
        _cancel_eft_transactions(
            pos_id,
            model,
            order_id,
            is_mobile,
            canceled_by,
        )
        _print_tef_receipts(
            pos_id,
            is_mobile,
        )

        if show_cancel_message:
            sysactions.show_messagebox(pos_id, "$ORDER_VOIDED_WITH_SUCCESS")

        return True
    except sysactions.StopAction as _:
        return False
    except Exception as _:
        logger.exception("Error voiding order {}".format(str(order_id) if order_id else 'undefined'))
        if show_cancel_message:
            sysactions.show_messagebox(pos_id, "$ERROR_CANCEL_ORDER")

        return False


def _void_order(
    order_id,           # type: str
    originator_pos_id,  # type: str
    void_reason,        # type: Dict
):
    # type: (...) -> None

    for _ in range(VOID_ATTEMPTS):
        try:
            void_order(originator_pos_id, order_id=order_id, void_reason=void_reason)
            break
        except Exception as _:
            logger.exception("Error voiding order")
            sleep(1)


def _print_tef_receipts(
    pos_id,     # type: str
    is_mobile,  # type: bool
):
    # type: (...) -> None

    if not is_mobile:
        from posactions import get_printer_receipt
        printer_receipt = get_printer_receipt()
        printer_receipt.print_tef_receipts(pos_id, cancel_receipt=True)


def _cancel_eft_transactions(
    pos_id,         # type: str
    model,          # type: eTree.XML
    order_id,       # type: str
    is_mobile,      # type: bool
    canceled_by,    # type: str
):
    # type: (...) -> None

    from posactions import is_table_service
    is_table_service = is_table_service(model)

    cancel_eft_transactions(
        pos_id,
        model,
        order_id,
        tender=None,
        is_table_service=is_table_service,
        is_mobile=is_mobile,
        operator_id=canceled_by
    )


def _get_canceled_by(
    pos_id,     # type: str
    model,      # type: eTree.XML
):
    # type: (...) -> str

    authenticated_manager = get_menu_manager_authenticated_user(int(pos_id))
    if authenticated_manager is not None:
        canceled_by = authenticated_manager
    else:
        operator = model.find("Operator")
        canceled_by = operator.get("id", "UNDEFINED") if operator is not None else "UNDEFINED"

    return canceled_by


def _void_fiscal_order(
    pos_id,  # type: str
    model,  # type: eTree.XML
):
    # type: (...) -> (str, str)

    order_id = ""
    pos_ot = sysactions.get_posot(model)
    limit_datetime = datetime.now() - timedelta(minutes=30)
    date_format = "%Y-%m-%dT%H:%M:%S"
    orders = pos_ot.listOrders(state="PAID", since=limit_datetime.strftime(date_format))
    order_options = []
    baseurl = "/mwapp/services/PosController/POS{}/?token=TK_POS_EXECUTEACTION&format=2&isBase64=true&payload=" \
        .format(pos_id)
    order_descr = "{} - {} POS{:02} - {:05} - R${:.2f}"
    for order in orders:
        if 'custom_properties' in order and 'FISCALIZATION_DATE' in order['custom_properties']:
            fiscalization_prop = order['custom_properties']['FISCALIZATION_DATE']
            fiscalization_date = convert_from_utf_to_localtime(datetime.strptime(fiscalization_prop, date_format))
            if fiscalization_date.replace(tzinfo=None) > limit_datetime:
                descr = order_descr.format(
                    fiscalization_date.strftime('%H:%M'),
                    order['podType'],
                    int(order['originatorId'][3:]),
                    int(order['orderId']),
                    float(order['totalGross'])
                )
                payload = base64.b64encode("\0".join(("doOrderPicture", pos_id, order['orderId'])))
                url = baseurl + payload
                order_options.append((order['orderId'], descr, url))

    if len(order_options) > 0:
        selected = sysactions.show_order_preview(pos_id,
                                                 order_options,
                                                 "$SELECT_AN_ORDER_TO_CANCEL",
                                                 buttons="CANCEL|OK",
                                                 onthefly=True)
        if (selected is None) or (selected[0] == "0"):
            raise sysactions.StopAction()
        order_id = selected[1]

    if order_id:
        originator_pos_id = int(next(iter([x for x in orders if x['orderId'] == order_id]))['originatorId'][3:])
        void_fiscal_order(originator_pos_id, order_id)
    else:
        sysactions.show_messagebox(pos_id, "$NO_ORDER_FOUND_TO_CANCEL")
        raise sysactions.StopAction()

    return order_id, originator_pos_id
