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 application.interactor import OrderInteractor
from custom_sysactions import user_authorized_to_execute_action
from manager.reports import justification_report
from utilfunctions import get_custom_property_value
from utilconfigurations import must_require_void_reason
from utils import (
    get_void_reason_id,
    void_order,
    void_fiscal_order,
    get_canceled_by,
    get_fixed_void_reason,
)
from .do_void_remote_order import (
    void_remote_order,
)
from .. import logger
VOID_ATTEMPTS = 3


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

    is_fiscal_void = fiscal_void.lower() == 'true'
    is_mobile = str(is_mobile).lower() == 'true'

    try:
        model = sysactions.get_model(pos_id)

        auth_user = auth_data if auth_data is not None else None
        canceled_by = get_canceled_by(
            pos_id,
            model,
            auth_user
        )

        if is_fiscal_void:
            order_id, void_reason = _void_fiscal_order(
                pos_id=pos_id,
                canceled_by=canceled_by
            )
        elif received_void_reason is None:
            void_reason = _void_non_fiscal_order(
                pos_id=pos_id,
                order_id=order_id,
                model=model,
                canceled_by=canceled_by,
            )
        else:
            void_reason = get_fixed_void_reason(
                reason=received_void_reason,
                canceled_by=canceled_by,
            )

        remote_order_id = _void_partner_order(
            pos_id=pos_id,
            order_id=order_id,
        )
        if remote_order_id:
            sysactions.show_messagebox(pos_id, "$REMOTE_ORDER_WILL_BE_VOIDED")
            raise sysactions.StopAction()

        _void_order(
            pos_id=pos_id,
            order_id=order_id,
            void_reason=void_reason,
        )

        _cancel_eft_transactions(
            pos_id=pos_id,
            model=model,
            order_id=order_id,
            is_mobile=is_mobile,
            canceled_by=canceled_by,
        )

        justification_type = 'VOID_PAID_ORDER' if is_fiscal_void else 'VOID_CURRENT_ORDER'
        justification_report(
            pos_id=pos_id,
            justification_type=justification_type,
            justification=void_reason.get('VOID_REASON_DESCR'),
            authorized_by=canceled_by,
            order_id=order_id if order_id not in ["", "None", None] else None
        )

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

        return True

    except sysactions.StopAction as _:
        return False

    except Exception as _: # noqa
        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_partner_order(
    pos_id,     # type: str
    order_id,   # type: str
):
    # type: (...) -> str

    remote_order_id = get_custom_property_value(
        pos_id=pos_id,
        custom_property="REMOTE_ORDER_ID",
        order_id=order_id,
    )

    if remote_order_id is not None:
        void_remote_order(
            pos_id=pos_id,
            remote_id=remote_order_id,
        )

    return remote_order_id


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

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


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

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


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

    _print_tef_receipts(
        pos_id=pos_id,
        is_mobile=is_mobile,
        order_id=order_id,
    )


def _void_non_fiscal_order(
        pos_id,             # type: str
        order_id,           # type: str
        model,              # type: eTree.XML
        canceled_by,        # type: str
):
    void_reason = get_fixed_void_reason(
        reason="USER_CANCELLATION",
        canceled_by=canceled_by,
    )

    void_type = "VOID_CURRENT_ORDER"
    if must_require_void_reason(void_type):
        void_reason = get_void_reason_id(
            pos_id=pos_id,
            void_type=void_type,
            authentication_user=canceled_by,
        )
        if not void_reason:
            raise sysactions.StopAction()

    if order_id in ["", None, "None"]:
        sysactions.check_current_order(pos_id, model)

    return void_reason


def _void_fiscal_order(
    pos_id,         # type: str
    canceled_by,    # type: str
):
    # type: (...) -> (str, dict)

    order_id = ""
    limit_datetime = datetime.utcnow() - timedelta(minutes=30)
    date_format = "%Y-%m-%dT%H:%M:%S"

    fiscalized_orders = OrderInteractor(pos_id=pos_id).get_fiscalized_orders(
        from_date=limit_datetime.strftime(date_format),
        pos_list=sysactions.get_poslist(),
    )

    order_options = []
    baseurl = "/mwapp/services/PosController/POS{}/?token=TK_POS_EXECUTEACTION&format=2&isBase64=true&payload="
    baseurl = baseurl.format(pos_id)
    order_descr = "{} - {} POS{:02} - {:05} - R${:.2f}"

    for order in fiscalized_orders:
        descr = order_descr.format(
            convert_from_utf_to_localtime(datetime.strptime(order.fiscalization_date, date_format)).strftime('%H:%M'),
            order.pod_type,
            int(order.originator_id[3:]),
            int(order.order_id),
            float(order.total_gross) + float(order.tip)
        )
        payload = base64.b64encode("\0".join(("doOrderPicture", pos_id, order.order_id)))
        url = baseurl + payload
        order_options.append((order.order_id, 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:
        remote_order_id = _void_partner_order(
            pos_id=pos_id,
            order_id=order_id,
        )
        if remote_order_id:
            sysactions.show_messagebox(pos_id, "$REMOTE_ORDER_WILL_BE_VOIDED")
            raise sysactions.StopAction()

        void_reason = get_fixed_void_reason(
            reason="USER_FISCAL_CANCELLATION",
            canceled_by=canceled_by,
        )

        void_type = "VOID_PAID_ORDER"
        if must_require_void_reason(void_type):
            void_reason = get_void_reason_id(
                pos_id=pos_id,
                void_type=void_type,
                authentication_user=canceled_by
            )
            if not void_reason:
                raise sysactions.StopAction()

        originator_pos_id = int([x for x in fiscalized_orders if x.order_id == order_id][0].originator_id[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, void_reason
