import base64

from datetime import datetime, timedelta
from time import sleep

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


from actions.eft import (
    check_eft_is_valid,
    EftConstants,
    get_electronic_tenders,
    has_electronic_payment,
)

from helper import translate_pos_label
from mw_helper import show_message_options_dialog
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,
    is_stored_order,
)
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,
    do_confirm_void,
)
from .do_void_remote_order import (
    void_remote_order,
)
from .. import logger
from ..config.is_tef_available import check_tef_connectivity
from utilfunctions import send_order_canceled_event
from ..eft.cancel_eft_transaction import clear_multiple_tenders

VOID_ATTEMPTS = 3

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

    model = sysactions.get_model(posid=pos_id)
    stored_order = is_stored_order(model=model)

    if from_timeout:
        _void_order(
            pos_id=pos_id,
            fiscal_void=fiscal_void,
            order_id=order_id,
            show_cancel_message=show_cancel_message,
            is_mobile=is_mobile,
            eft_processor=eft_processor,
            auth_data=auth_data,
            received_void_reason=received_void_reason,
            order_is_stored=stored_order,
        )
        return False

    if stored_order:
        return do_void_stored_order(
            pos_id,
            fiscal_void=fiscal_void,
            order_id=order_id,
            show_cancel_message=show_cancel_message,
            is_mobile=is_mobile,
            eft_processor=eft_processor,
            auth_data=auth_data,
            received_void_reason=received_void_reason,
        )
    else:
        return do_void_regular_order(
            pos_id,
            fiscal_void=fiscal_void,
            order_id=order_id,
            show_cancel_message=show_cancel_message,
            is_mobile=is_mobile,
            eft_processor=eft_processor,
            auth_data=auth_data,
            received_void_reason=received_void_reason,
        )


@user_authorized_to_execute_action
def do_void_regular_order(
    pos_id,                 # type: str
    fiscal_void,            # type: str
    order_id,               # type: str
    show_cancel_message,    # type: bool
    is_mobile,              # type: bool
    eft_processor,          # type: str
    auth_data,              # type: str
    received_void_reason,   # type: str
):
    # type: (...) -> bool

    return _void_order(
        pos_id=pos_id,
        fiscal_void=fiscal_void,
        order_id=order_id,
        show_cancel_message=show_cancel_message,
        is_mobile=is_mobile,
        eft_processor=eft_processor,
        auth_data=auth_data,
        received_void_reason=received_void_reason,
    )


@user_authorized_to_execute_action
def do_void_stored_order(
    pos_id,                 # type: str
    fiscal_void,            # type: str
    order_id,               # type: str
    show_cancel_message,    # type: bool
    is_mobile,              # type: bool
    eft_processor,          # type: str
    auth_data,              # type: str
    received_void_reason,   # type: str
):
    # type: (...) -> bool

    return _void_order(
        pos_id=pos_id,
        fiscal_void=fiscal_void,
        order_id=order_id,
        show_cancel_message=show_cancel_message,
        is_mobile=is_mobile,
        eft_processor=eft_processor,
        auth_data=auth_data,
        received_void_reason=received_void_reason,
        order_is_stored=True,
    )


def _void_order(
    pos_id,                 # type: str
    fiscal_void,            # type: str
    order_id,               # type: str
    show_cancel_message,    # type: bool
    is_mobile,              # type: bool
    eft_processor,          # type: str
    auth_data,              # type: str
    received_void_reason,   # type: str
    order_is_stored=False,  # type: Optional[bool]
):
    # type: (...) -> bool

    is_fiscal_void = fiscal_void.lower() == 'true'
    is_mobile = str(is_mobile).lower() == 'true'
    logger.debug("eft_processor voiding order {}".format(str(eft_processor)))
    void_type = 'VOID_PAID_ORDER' if is_fiscal_void else 'VOID_CURRENT_ORDER'
    void_type = 'VOID_STORED_ORDER' if order_is_stored else void_type

    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,
                eft_processor=eft_processor,
                order_id=order_id,
            )
        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,
                void_type=void_type,
            )

        _void_current_order(
            pos_id=pos_id,
            order_id=order_id,
            void_reason=void_reason,
        )

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

        justification_report(
            pos_id=pos_id,
            justification_type=void_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
        )

        send_order_canceled_event(
            pos_id=pos_id,
            order_id=order_id,
        )

        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: (...) -> None

    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,
        )
        raise sysactions.StopAction()


def _void_current_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 _cancel_transactions(
    pos_id,         # type: str
    model,          # type: eTree.XML
    order_id,       # type: str
    is_mobile,      # type: bool
    canceled_by,    # type: str
):
    # type: (...) -> None

    order_taker = sysactions.get_posot(model=model)
    order_picture = order_taker.orderPicture(
        posid=pos_id,
        orderid=order_id or ""
    )

    order_xml = eTree.XML(order_picture)
    order = order_xml.find("Order") if order_xml.find("Order") else order_xml

    clear_multiple_tenders(
        pos_id=pos_id,
        order=order,
        order_id=order_id,
        is_mobile=is_mobile,
        operator_id=canceled_by,
    )


def validate_eft_processor_order(
    eft_processor,      # type: str
    pos_id,             # type: str
    order_id,           # type: str
):
    # type: (...) -> None

    model = sysactions.get_model(posid=pos_id)
    order_taker = sysactions.get_posot(model=model)
    order_picture = order_taker.orderPicture(
        posid=pos_id,
        orderid=order_id,
    )

    order_xml = eTree.XML(order_picture)
    order = order_xml.find("Order") if order_xml.find("Order") else order_xml

    if not has_electronic_payment(order=order):
        return

    tef_status = check_tef_connectivity(pos_id=pos_id)
    if tef_status == "false":
        resp = show_message_options_dialog(
            pos_id=pos_id,
            buttons="$YES|$NO",
            title="$CONFIRMATION",
            message="$ORDER_CANCELLED_NO_REFUND",
        )
        if resp == 1:
            raise sysactions.StopAction()

    elif tef_status == EftConstants.INVALID_ELECTRONIC_PAYMENT_CONFIGURATION:
        sysactions.show_messagebox(
            posid=pos_id,
            message="$ERROR_CANCEL_ORDER_INVALID_POS_CONFIGURATION",
        )
        raise sysactions.StopAction()

    eft_is_valid = check_eft_is_valid(eft_processor=eft_processor)
    if not eft_is_valid:
        return

    tenders = get_electronic_tenders(order=order)
    for tender in tenders:
        is_electronic_payment = has_electronic_payment(
            order=None,
            tender_electronic_type=tender.get("electronicType"),
        )

        if is_electronic_payment:
            tender_detail = tender.get("detail")
            transaction_processor = tender_detail.get("TransactionProcessor", "")
            if transaction_processor.lower() != eft_processor.lower():
                message = "$ERROR_CANCEL_ORDER_ANOTHER_EFT_PROCESSOR|{}".format(transaction_processor)
                sysactions.show_messagebox(
                    posid=pos_id,
                    message=message,
                )
                raise sysactions.StopAction()


def _void_non_fiscal_order(
    pos_id,         # type: str
    order_id,       # type: str
    model,          # type: eTree.XML
    canceled_by,    # type: str
):
    # type: (...) -> Optional[Dict]

    _void_partner_order(
        pos_id=pos_id,
        order_id=order_id,
    )

    if is_stored_order(
        model=model,
        pos_id=pos_id,
        order_id=order_id,
    ):
        void_type = "VOID_STORED_ORDER"
    else:
        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()
    else:
        do_confirm_void(pos_id=pos_id)
        void_reason = get_fixed_void_reason(
            reason="USER_CANCELLATION",
            canceled_by=canceled_by,
            void_type=void_type,
        )

    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
    eft_processor,      # type: Optional[str]
    order_id=None,      # type: Optional[str]
):
    # type: (...) -> (str, dict)

    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 = "{} - {} {}{:02} - {:05} - R${:.2f}"
    model = sysactions.get_model(posid=pos_id)
    pos = translate_pos_label(model=model)

    if order_id in ['', None]:
        for order in fiscalized_orders:
            fiscalization_date = datetime.strptime(order.fiscalization_date, date_format)
            descr = order_descr.format(
                convert_from_utf_to_localtime(utc=fiscalization_date).strftime('%H:%M'),
                order.pod_type,
                pos,
                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:
        order_id = _get_order_id_by_dialog(
            pos_id=pos_id,
            options=order_options,

        )

    if not order_id:
        sysactions.show_messagebox(
            posid=pos_id,
            message="$NO_ORDER_FOUND_TO_CANCEL",
        )
        raise sysactions.StopAction()

    validate_eft_processor_order(
        eft_processor=eft_processor,
        pos_id=pos_id,
        order_id=order_id,
    )

    _void_partner_order(
        pos_id=pos_id,
        order_id=order_id,
    )

    void_type = "VOID_PAID_ORDER"
    if must_require_void_reason(void_on=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()
    else:
        do_confirm_void(pos_id=pos_id)
        void_reason = get_fixed_void_reason(
            reason="USER_FISCAL_CANCELLATION",
            canceled_by=canceled_by,
            void_type=void_type,
        )
        if not void_reason:
            raise sysactions.StopAction()

    void_fiscal_order(
        pos_id=int(pos_id),
        order_id=int(order_id),
        justification=void_reason.get("VOID_REASON_DESCR", None),
    )

    return order_id, void_reason


def _get_order_id_by_dialog(
    pos_id,         # type: str
    options,  # type: List
):
    # type: (...) -> str

    selected = sysactions.show_order_preview(
        posid=pos_id,
        orders=options,
        title="$SELECT_AN_ORDER_TO_CANCEL",
        buttons="CANCEL|OK",
        onthefly=True
    )
    if (selected is None) or (selected[0] == "0"):
        raise sysactions.StopAction()

    return selected[1]
