import json
import logging
import base64

from xml.etree import cElementTree as eTree
from bustoken import (
    TK_ELECTRONIC_PAYMENT_FINISH_PAYMENT,
    TK_ELECTRONIC_PAYMENT_NOT_ALL_PAYMENTS_REFUNDED,
    TK_ELECTRONIC_PAYMENT_REFUND_CANCELED
)
from msgbus import FM_PARAM, TK_SYS_ACK
from sysactions import (
    mbcontext,
    show_messagebox,
    StopAction
)

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

logger = logging.getLogger("TableActions")


def finish_eft_transactions(
    pos_id,                 # type: str
    order,                  # type: eTree
    status,                 # type: str
    tender_id="",           # type: str
    is_table_service=False  # type: bool
):
    # type: (...) -> None
    from actions.config.is_tef_available import get_eft_processor_name

    totaled_states = order.findall("StateHistory/State[@state='TOTALED']")
    if len(totaled_states) == 0:
        return

    electronic_tenders = get_electronic_tenders(order)
    if len(electronic_tenders) == 0:
        return

    component_name = get_electronic_payment_component_name(pos_id=pos_id)
    if component_name is None:
        return

    eft_processor = get_eft_processor_name(pos_id=pos_id)
    if not check_eft_is_valid(eft_processor=eft_processor):
        return

    order_id = order.get("orderId")
    last_totaled = totaled_states[-1]
    last_totaled_date = last_totaled.get("timestamp").replace("-", "").replace(":", "")
    fiscal_date = last_totaled_date[:8]
    fiscal_time = last_totaled_date[9:15]
    status = status if has_electronic_payment(order) or is_table_service else "0"
    tender_details_str = base64.b64encode(json.dumps(electronic_tenders))
    data = ";".join([str(pos_id), order_id, fiscal_date, fiscal_time, status, tender_details_str, str(tender_id)])
    _finish_transaction(pos_id, order_id, status, data, component_name)


def _finish_transaction(
    pos_id,             # type: str
    order_id,           # type: str
    status,             # type: str
    data,               # type: str
    component_name,     # type: str
):
    # type: (...) -> None

    for try_num in range(1, 4):
        try:
            msg = "[finish_eft_transactions] Trying to send a finish message. Order #: {}; Try #: {}; STATUS #: {}"
            logger.info(msg.format(order_id, try_num, status))
            ten_minutes_timeout = 600000000
            ret = mbcontext.MB_EasySendMessage(
                dest_name=component_name,
                token=TK_ELECTRONIC_PAYMENT_FINISH_PAYMENT,
                format=FM_PARAM,
                data=data,
                timeout=ten_minutes_timeout
            )

            if ret.token == TK_SYS_ACK:
                logger.info("[finish_eft_transactions] Success finished eft transaction. Order #: {}".format(order_id))
                return
            elif ret.token == TK_ELECTRONIC_PAYMENT_NOT_ALL_PAYMENTS_REFUNDED:
                show_messagebox(pos_id, "$SOME_PAYMENTS_WERE_NOT_REFUNDED")
                continue
            elif ret.token == TK_ELECTRONIC_PAYMENT_REFUND_CANCELED:
                raise StopAction()

            message = "[finish_eft_transactions] ACK not received. Order #: {} - {} - {}"
            logger.info(message.format(order_id, ret.token, ret.data))
            _show_internal_error_message(pos_id, status)

        except Exception as ex:
            if isinstance(ex, StopAction):
                raise ex

            _show_internal_error_message(pos_id, status)

            msg = "[finish_eft_transactions] Error sending message. Order #: {} / Try #: {} / Status: {}"
            msg = msg.format(order_id, try_num, status)
            if try_num == 3:
                logger.exception(msg, exc_info=True)
            else:
                logger.info(msg)

    _show_manual_process_message(pos_id, status)


def _show_internal_error_message(
    pos_id,     # type: str
    status      # type: str
):
    # type: (...) -> None

    _display_message(
        pos_id,
        status,
        "$INTERNAL_ERROR_REFUNDING_ELECTRONIC_PAYMENTS",
        "$INTERNAL_ERROR_CONFIRMING_ELECTRONIC_PAYMENTS"
    )


def _show_manual_process_message(
    pos_id,     # type: str
    status      # type: str
):
    # type: (...) -> None

    _display_message(
        pos_id,
        status,
        "$ERROR_REFUNDING_ELECTRONIC_PAYMENTS_MANUAL_PROCESS_NEEDED",
        "$ERROR_CONFIRMING_ELECTRONIC_PAYMENTS_MANUAL_PROCESS_NEEDED"
    )


def _display_message(
    pos_id,             # type: str
    status,             # type: str
    error_refunding,    # type: str
    error_confirming    # type: str
):
    # type: (...) -> None

    message = error_refunding if status == "0" else error_confirming
    show_messagebox(pos_id, message)
