import json
import logging
import base64

from xml.etree import cElementTree as eTree
from actions.eft import get_electronic_tenders, has_electronic_payment
from bustoken import TK_SITEF_FINISH_PAYMENT, TK_SITEF_NOT_ALL_PAYMENTS_REFUNDED, TK_SITEF_REFUND_CANCELED
from msgbus import FM_PARAM, TK_SYS_ACK
from sysactions import (
    mbcontext,
    get_model,
    get_used_service,
    show_messagebox,
    StopAction
)

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

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

    model = get_model(pos_id)
    component_name = get_used_service(model, "eft")
    if component_name is None:
        return

    last_totaled = totaled_states[-1]
    last_totaled_date = last_totaled.get("timestamp").replace("-", "").replace(":", "")

    order_id = order.get("orderId")
    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"
    tenders = get_electronic_tenders(order)
    tender_details_str = base64.b64encode(json.dumps(tenders))

    data = ";".join([str(pos_id), order_id, fiscal_date, fiscal_time, status, tender_details_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(
                component_name,
                TK_SITEF_FINISH_PAYMENT,
                FM_PARAM,
                data,
                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_SITEF_NOT_ALL_PAYMENTS_REFUNDED:
                show_messagebox(pos_id, "$SOME_PAYMENTS_WERE_NOT_REFUNDED")
                continue
            elif ret.token == TK_SITEF_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)
