import base64
import os
import re
import time
from ctypes import (
    Array,
    addressof,
    c_char,
    c_long,
    create_string_buffer
)
from datetime import datetime
from logging import Logger
from threading import Lock
from xml.etree import cElementTree as eTree

from cfgtools import Group
from messagebus import DataType, DefaultToken, Message
from mbcontextmessagehandler import MbContextMessageBus
from modelcommon.customexception import SitefUnavailableException
from modelcommon.model import SitefResponse
from modelcommon.sitef import (
    SitefFunctions,
    ResultCodes,
    CommandCodes,
    FieldCodes,
    FinalizeActionFunction,
    CancelCodes
)
from pos_model import TenderType
from typing import (
    Any,
    List,
    Optional,
    Union
)

from ._SitefActions import SitefActions
from ._SitefParams import SitefParams
from ._SitefServiceFinder import SitefServiceFinder
from dialogmanager import DialogManager
from model import (
    CancelPaymentOrderData,
    ConfirmFinishPaymentData,
    FinishPaymentData,
    PaymentResponse,
    PaymentResponseData,
    PaymentResponseMessage,
    StartPaymentData,
    TestConnectionData
)
from util import get_transaction_identifier

TK_STORECFG_GET = 0x01300001
TK_SLCTRL_OMGR_ORDERPICT = 0x00A00002

API_SUCCESS_OK = 0
API_LOOP_OK = 10000
API_LOOP_FINISHED = 0
API_NO_SITEF_COMMUNICATION = -5


def _get_terminal_id(
    pdv_tef     # type: int
):
    # type: (...) -> str

    return "SW%06d" % pdv_tef


def _api_has_data(command):
    return command.value == API_SUCCESS_OK


def _format_api_error_response(api_ret):
    error_responses = {
        -2: PaymentResponseMessage("Cancelado"),
        -5: PaymentResponseMessage("Falha no Servidor SiTEF"),
        -6: PaymentResponseMessage("Operacao cancelada pelo usuario"),
        -40: PaymentResponseMessage("Nao Autorizado - SiTEF"),
        -43: PaymentResponseMessage("Erro PinPad"),
        -120: PaymentResponseMessage("Cancelado")
    }

    if api_ret > 0:
        return PaymentResponseMessage("Nao Autorizado")

    return error_responses.get(api_ret, PaymentResponseMessage("Falha na Operacao: Erro {}".format(api_ret)))


def _should_ignore_command(command):
    return command.value in (2, 4, 21, 14, 52)


def _get_dll_path(config):
    dll_path = config.find_value("DLLPath", "")
    for var in re.findall("{[a-zA-Z0-9]*}", dll_path):
        dll_path = dll_path.replace(var, os.environ.get(var.replace('{', '').replace('}', ''), ""))

    return os.path.normpath(os.path.join(dll_path, "clisitef32i.dll"))


class SitefPaymentProcessor(object):
    def __init__(
        self,
        message_bus,        # type: MbContextMessageBus
        dialog_manager,     # type: DialogManager
        config,             # type: Group
        logger              # type: Logger
    ):
        # type: (...) -> None

        from ctypes import WinDLL
        self.logger = logger
        self.dialog_manager = dialog_manager
        self.message_bus = message_bus

        self.store_id = config.find_value("IdLoja")
        self.timeout = int(config.find_value("Timeout", 60))
        self.show_qr_code = config.find_value("ShowQRCode")
        self.is_sync = config.find_value("IsMobileSynch", "False") == "True"
        self.pdv_tef = int(config.find_value("PdvTef") or config.find_value("PosId"))

        dll_path = _get_dll_path(config)
        self.sitef_dll = WinDLL(dll_path)
        self.configura_sitef_interativo = self.sitef_dll["ConfiguraIntSiTefInterativo"]
        self.inicia_sitef_interativo = self.sitef_dll["IniciaFuncaoSiTefInterativo"]
        self.continua_sitef_interativo = self.sitef_dll["ContinuaFuncaoSiTefInterativo"]
        self.finaliza_sitef_interativo = self.sitef_dll["FinalizaFuncaoSiTefInterativo"]

        self.service_finder = SitefServiceFinder(
            self.timeout,
            config.find_values("IP") or [],
            self.store_id,
            self.pdv_tef,
            self.logger,
            dll_path
        )

        self.current_ip = None
        self.current_order = None

        self.cancel = False
        self.cancel_lock = Lock()

    def stop(self):  # type: () -> None
        self.service_finder.stop_search_thread()

    def connection_status(
        self,
        test_connection_data    # type: TestConnectionData
    ):
        # type: (...) -> bool

        return self.service_finder.process(
            pos_id=test_connection_data.pos_id,
            data_fiscal=test_connection_data.fiscal_date,
            hora_fiscal=test_connection_data.fiscal_time
        )

    def add_payment(self, start_payment_data, additional_params=""):
        self.service_finder.lock_sitef()
        try:
            result = self._process_loop(start_payment_data, additional_params)
        finally:
            self.dialog_manager.close_dialogs(start_payment_data.pos_id)
            self.service_finder.release_sitef()

        return result

    def cancel_payment_order(
        self,
        cancel_payment_order_data   # type: CancelPaymentOrderData
    ):
        # type: (...) -> Any[Union[SitefResponse, str]]

        try:
            self.service_finder.lock_sitef()
            return self.execute_cancel_payment_order(cancel_payment_order_data)
        finally:
            pos_id = cancel_payment_order_data.pos_id
            self.dialog_manager.close_dialogs(pos_id)
            self.service_finder.release_sitef()

    def execute_cancel_payment_order(
        self,
        cancel_payment_order_data    # type: CancelPaymentOrderData
    ):
        # type: (...) -> Any[Union[SitefResponse, str]]

        pos_id = cancel_payment_order_data.pos_id
        tender_type = cancel_payment_order_data.tender_type
        tender_amount = cancel_payment_order_data.tender_amount
        order_id = cancel_payment_order_data.order_id
        fiscal_date = str(cancel_payment_order_data.fiscal_date)
        fiscal_time = str(cancel_payment_order_data.fiscal_time)
        tender_id = cancel_payment_order_data.tender_id

        self.logger.debug("SitefProcessor Cancel payment order - STARTED")

        self._check_pin_pad(pos_id)
        self._check_sitef_is_available()
        self._notify_start_process(
            pos_id=int(pos_id),
            tender_type=str(tender_type)
        )

        self.cancel = False
        type_code = SitefFunctions.get_cancel_code(tender_type=tender_type)
        amount = "{:,.2f}".format(float(tender_amount)).replace(",", "").replace(".", ",")
        transaction_identifier = get_transaction_identifier(
            order_id=order_id,
            tender_id=tender_id
        )
        result = self.inicia_sitef_interativo(
            type_code,
            amount,
            transaction_identifier,
            fiscal_date,
            fiscal_time,
            "",
            ""
        )
        document_to_cancel = self._get_document_to_cancel(
            type_code,
            cancel_payment_order_data
        )

        if result == API_LOOP_OK:
            response = SitefResponse()
            response.transaction_identifier = transaction_identifier
            command = c_long(0)
            field_type = c_long(0)
            min_size = c_long(0)
            max_size = c_long(0)
            buffer_size = 32768
            output_buffer = create_string_buffer('\000' * buffer_size)

            last_time = -1
            last_message = ""

            show_message_commands = (
                SitefActions.SHOW_OPERATOR_MESSAGE,
                SitefActions.SHOW_OPERATOR_AND_CUSTOMER_MESSAGE,
            )

            self.current_order = order_id
            while result == API_LOOP_OK:
                with self.cancel_lock:
                    if self.cancel:
                        keep_going = -1 if tender_type == 122 else 2
                    else:
                        keep_going = 0

                result = self.continua_sitef_interativo(
                    addressof(command),
                    addressof(field_type),
                    addressof(min_size),
                    addressof(max_size),
                    output_buffer,
                    buffer_size,
                    keep_going
                )

                if result != API_LOOP_OK:
                    break

                if not _api_has_data(command):
                    self.logger.debug("COMMAND TYPE: {0} - BUFFER: {1}".format(command.value, output_buffer.value))

                    if command.value in show_message_commands:
                        key_cancel = "$INSERT_CARD_CANCEL_PAYMENT|{}".format(cancel_payment_order_data.card_number)
                        data = (command, output_buffer, key_cancel, None)
                        last_message = self._call_command_function(*data)
                        if "insira" not in str(output_buffer.value).lower():
                            self.dialog_manager.show_message(pos_id, output_buffer.value)
                    elif command.value == CommandCodes.set_amount_transaction:
                        output_buffer.value = amount
                    elif self._idle_field(command, field_type, FieldCodes.cancel_transaction_date):
                        output_buffer.value = datetime.strptime(fiscal_date, '%Y%m%d').strftime('%d%m%Y')
                    elif self._idle_field(command, field_type, FieldCodes.cancel_document_number):
                        output_buffer.value = str(document_to_cancel)
                    elif command.value == CommandCodes.idle_command:
                        data = (pos_id, order_id, last_time, output_buffer, max_size, last_message)
                        last_time = self._wait_action(*data)
                    elif command.value == SitefActions.MENU_CONTENT and type_code == CancelCodes.digital_wallet:
                        choices = {option.split(':')[1]: option.split(':')[0] for option
                                   in output_buffer.value.split(';') if option}
                        output_buffer.value = str(choices[cancel_payment_order_data.media])

                if field_type.value != FieldCodes.empty_information:
                    self.logger.debug("FIELD TYPE: {0} - BUFFER: {1}".format(field_type.value, output_buffer.value))
                    response = self._set_response_cancel_data(
                        response=response,
                        field_type=field_type.value,
                        output_buffer=output_buffer
                    )

            if result == ResultCodes.success:
                self._notify_end_process(pos_id=pos_id)
                confirm = FinalizeActionFunction.confirm
                self.finaliza_sitef_interativo(
                    confirm,
                    transaction_identifier,
                    fiscal_date,
                    fiscal_time,
                    None
                )
                self.logger.debug("SitefProcessor Cancel payment order - SUCCESS")
                return response

            cancel = FinalizeActionFunction.cancel
            self.finaliza_sitef_interativo(
                cancel,
                transaction_identifier,
                fiscal_date,
                fiscal_time,
                None
            )
            self._notify_end_process(pos_id=pos_id)

            return "$ERROR_CANCEL_ELECTRONIC_PAYMENTS_ORDER"

    @staticmethod
    def _get_document_to_cancel(
        type_code,                  # type: int
        cancel_payment_order_data   # type: CancelPaymentOrderData
    ):
        # type: (...) -> str

        auth_code = cancel_payment_order_data.auth_code
        nsu_host = cancel_payment_order_data.nsu_host

        return auth_code if type_code == CancelCodes.digital_wallet else nsu_host

    def cancel_payment(self):
        with self.cancel_lock:
            self.cancel = True
        return ""

    def finish_payment(
        self,
        finish_payment_data     # type: FinishPaymentData
    ):
        # type: (...) -> bool

        self.logger.debug("SiTEF finish payment started")
        order_id = finish_payment_data.order_id
        pos_id = finish_payment_data.pos_id
        tender_id = finish_payment_data.tender_id
        status = int(finish_payment_data.status)
        transaction_identifier = get_transaction_identifier(
            order_id=order_id,
            tender_id=tender_id
        )
        try:
            self.service_finder.lock_sitef()
            if not self.current_order:
                self.logger.debug("No order to finish, ignoring...")
                return True

            if self.current_order != order_id:
                self.logger.error("order to finish = {}, current order = {}".format(order_id, self.current_order))
                return True

            self.logger.debug("will finish with data: {}".format(finish_payment_data))

            self.configura_sitef_interativo(
                str(self.current_ip),
                str(self.store_id),
                _get_terminal_id(self.pdv_tef),
                0
            )
            fiscal_date = str(finish_payment_data.fiscal_date)
            fiscal_time = str(finish_payment_data.fiscal_time)
            self.finaliza_sitef_interativo(status, transaction_identifier, fiscal_date, fiscal_time, None)

            self.current_ip = self.current_order = None
            self.logger.debug("SiTEF finish payment ended")
            return True
        except Exception as _:
            message = "SITEF Finalize - Pos: {0} / Order: {1} / Status: {2} / Coupon: {3}".format(
                pos_id,
                order_id,
                status,
                transaction_identifier
            )
            self.logger.exception(message)
            return False
        finally:
            self.service_finder.release_sitef()

    def confirm_finish_payment(
        self,
        confirm_finish_payment_data     # type: ConfirmFinishPaymentData
    ):
        # type: (...) -> None

        pass

    def _check_finalized_last_order(self, pos_id):
        for _ in xrange(0, 3, 1):
            if self.current_ip and self.current_order:
                order_id = self.current_order
                order = self._get_order_picture(pos_id, order_id)
                if not order:
                    continue
                order = eTree.XML(order).find("Order")
                created_at = order.get("createdAt").replace("-", "").replace(":", "")
                data_fiscal = created_at[:8]
                hora_fiscal = created_at[9:15]
                if order.find("StateHistory/State[@state='PAID']") is not None:
                    status = "1"
                elif order.find("StateHistory/State[@state='VOIDED']") is not None:
                    status = "0"
                else:
                    self.logger.error("Order %s sem status PAID ou VOIDED" % order_id)
                    return
                self.finish_payment(FinishPaymentData(pos_id, order_id, data_fiscal, hora_fiscal, status, ""))
            else:
                return

        self.logger.warning("Impossivel finalizar order %s" % self.current_order)
        self.current_order = self.current_ip = None

    def _get_order_picture(self, pos_id, order_id):
        try:
            pos_id_to_search = pos_id
            if order_id not in ('', None, 0):
                pos_id_to_search = ''
            params = (pos_id_to_search, order_id, 'false')
            reqbuf = '\x00'.join((str(p) for p in params))
            msg = self.message_bus.send_message("ORDERMGR%s" % pos_id, Message(TK_SLCTRL_OMGR_ORDERPICT, DataType.param, reqbuf))
            params = msg.data.split('\x00')
            if msg.token == DefaultToken.TK_SYS_NAK:
                return None
            return params[2]
        except Exception: # noqa
            self.logger.exception("Exception trying to get last order picture")
            return None

    def get_name(self):
        return "Sitef"

    def _check_pin_pad(
        self,
        pos_id,                 # type: int
        electronic_type=-1,     # type: int
    ):
        # type: (...) -> None

        if electronic_type == TenderType.digital_wallet:
            return

        self.dialog_manager.show_message(pos_id, "Conectando PinPad")
        time_limit = (time.time() + 5.0)
        while (time.time() < time_limit) and (not self.sitef_dll["VerificaPresencaPinPad"]()):
            time.sleep(0.2)

    def _process_loop(
        self,
        sitef_loop_data,         # type: StartPaymentData
        additional_params=""     # type: str
    ):
        # type: (...) -> PaymentResponse

        self.logger.debug("SiTEF loop started")
        self.cancel = False
        pos_id = sitef_loop_data.pos_id
        order_id = sitef_loop_data.order_id
        ip_tef = sitef_loop_data.ip_tef
        tender_id = sitef_loop_data.tender_id

        self._check_pin_pad(pos_id, sitef_loop_data.electronic_type)

        if self.current_order == order_id:
            ip_sitef = self.current_ip
        else:
            ip_sitef = self.service_finder.find_sitef_service() if ip_tef is None else ip_tef
            self._check_finalized_last_order(pos_id)

        if not ip_sitef:
            self.service_finder.wakeup_search()
            self.logger.warning("No IP available")
            raise SitefUnavailableException("Sem conexao SITEF")

        self._check_sitef_is_available()

        amount = sitef_loop_data.amount

        electronic_type = sitef_loop_data.electronic_type
        sitef_tender_type = None
        if electronic_type in (TenderType.debit, TenderType.voucher):
            sitef_tender_type = 2
        elif sitef_loop_data.electronic_type == TenderType.credit:
            sitef_tender_type = 3
        elif sitef_loop_data.electronic_type == TenderType.digital_wallet:
            sitef_tender_type = 122
        elif electronic_type == TenderType.connection_test:
            sitef_tender_type = 111

        transaction_identifier = get_transaction_identifier(
            order_id=order_id,
            tender_id=tender_id
        )
        fiscal_date = str(sitef_loop_data.fiscal_date)
        fiscal_time = str(sitef_loop_data.fiscal_time)
        operator = str(sitef_loop_data.operator)
        disabled_payments = "[10;17;18;19;27;28;29;34;35;36;42;]"
        api_ret = self.inicia_sitef_interativo(
            sitef_tender_type,
            amount,
            transaction_identifier,
            fiscal_date, fiscal_time,
            operator, "{};{}".format(disabled_payments, additional_params))

        if api_ret != API_LOOP_OK:
            self._log_api_start_ret_error(api_ret)
            return PaymentResponseMessage("Pagamento interrompido")

        self.current_order = order_id
        self.current_ip = ip_sitef

        command = c_long(0)
        field_type = c_long(0)
        min_size = c_long(0)
        max_size = c_long(0)
        buffer_size = 32768
        output_buffer = create_string_buffer('\000' * buffer_size)

        voided = False

        institution_name = ""
        card_bin = None
        transaction_date = None
        auth_code = None
        authorizer_name = None
        authorizer_cnpj = None
        card_type = None
        expiration_date = None
        nsu = None
        nsu_host = None
        transaction_processor = None
        receipt_merchant = None
        receipt_customer = None
        id_mercado_pago = None
        menu_title = ""
        last_digits = "0000"
        sat_acquirer = ""
        nfce_brand = ""

        show_message_commands = (
            SitefActions.SHOW_OPERATOR_MESSAGE,
            SitefActions.SHOW_OPERATOR_AND_CUSTOMER_MESSAGE,
        )

        while api_ret == API_LOOP_OK:
            keep_going = 0
            with self.cancel_lock:
                if self.cancel:
                    keep_going = -1 if electronic_type == 122 else 2

            api_ret = self.continua_sitef_interativo(
                addressof(command),
                addressof(field_type),
                addressof(min_size),
                addressof(max_size),
                output_buffer,
                buffer_size,
                keep_going
            )
            self.logger.debug("APRET: {0}".format(str(api_ret)))
            if api_ret != API_LOOP_OK:
                break

            self.logger.debug("COMMAND TYPE: {0} - BUFFER: {1}".format(command.value, output_buffer.value))
            if not _api_has_data(command) and not self._must_ignore_command(field_type):
                if command.value in show_message_commands:
                    self.dialog_manager.show_message(pos_id, output_buffer.value)
                elif command.value == SitefActions.SHOW_MESSAGE_WITH_CONFIRMATION:
                    self.dialog_manager.show_question(pos_id, output_buffer.value, buttons="$OK")
                elif command.value == SitefActions.SHOW_MESSAGE_WITH_CANCEL_BUTTON:
                    self.dialog_manager.show_message(pos_id, output_buffer.value)
                elif command.value == SitefActions.ASK_PIN_CODE:
                    pin = self.dialog_manager.show_input(pos_id, output_buffer.value, input_type="number")
                    if pin == "CANCELA":
                        self.cancel_payment()
                        output_buffer.value = "0" * max_size.value
                    else:
                        output_buffer.value = str(pin)
                elif command.value == SitefActions.CANCEL_OPERATION:
                    output_buffer.value = "0"
                    time.sleep(1.5)

                    if field_type.value != 111:
                        voided = True
                elif command.value == SitefActions.MENU_TITLE:
                    menu_title = output_buffer.value
                elif command.value == SitefActions.CLEAN_MENU_TITLE:
                    menu_title = ""
                elif command.value == SitefActions.MENU_CONTENT:
                    choices = {option.split(':')[0]: option.split(':')[1] for option in output_buffer.value.split(';') if option}
                    selected_choice = self.dialog_manager.show_choices(pos_id, choices, message=menu_title)
                    output_buffer.value = str(selected_choice)
                elif command.value == SitefActions.CLOSE_QRCODE:
                    self.dialog_manager.close_qrcode(pos_id)
                elif _should_ignore_command(command):
                    time.sleep(0.5)
                else:
                    self.dialog_manager.show_message(pos_id, "Mensagem {} {}".format(command.value, output_buffer.value))

            if field_type.value == SitefParams.CARD_BIN:
                card_bin = output_buffer.value
            elif field_type.value == SitefParams.TRANSACTION_PROCESSOR:
                transaction_processor = output_buffer.value
            elif field_type.value == SitefParams.CARD_TYPE:
                card_type = output_buffer.value
            elif field_type.value in SitefParams.AUTH_CODE:
                auth_code = output_buffer.value
            elif self._is_show_qrcode_command(field_type):
                self.dialog_manager.show_qrcode(pos_id, output_buffer.value)
            elif field_type.value == SitefParams.RECEIPT_CUSTOMER:
                receipt_customer = output_buffer.value
            elif field_type.value == SitefParams.RECEIPT_MERCHANT:
                receipt_merchant = output_buffer.value
            elif field_type.value == SitefParams.NSU:
                nsu = output_buffer.value
            elif field_type.value == SitefParams.NSU_HOST:
                nsu_host = output_buffer.value
            elif field_type.value == SitefParams.TRANSACTION_DATE:
                transaction_date = output_buffer.value
            elif field_type.value == SitefParams.AUTHORIZER_NAME:
                authorizer_name = output_buffer.value
            elif field_type.value == SitefParams.INSTITUTION_NAME:
                institution_name = output_buffer.value
            elif field_type.value == SitefParams.EXPIRATION_DATE:
                expiration_date = output_buffer.value
            elif field_type.value == SitefParams.AUTHORIZER_CNPJ:
                authorizer_cnpj = output_buffer.value
            elif field_type.value == SitefParams.ID_MERCADO_PAGO:
                id_mercado_pago = output_buffer.value
            elif field_type.value == SitefParams.DIGITAL_WALLET_ID:
                card_type = output_buffer.value
            elif field_type.value == SitefParams.LAST_CARD_DIGITS:
                last_digits = output_buffer.value
            elif field_type.value == SitefParams.SAT_ACQUIRER:
                sat_acquirer = output_buffer.value
            elif field_type.value == SitefParams.NFCE_BRAND:
                nfce_brand = output_buffer.value

        if api_ret == API_LOOP_FINISHED:
            self.logger.debug("SiTEF loop finished")
            return PaymentResponseData(
                media=institution_name.strip(),
                result=api_ret,
                card_number=card_bin,
                auth_code="{}{}".format(transaction_date, id_mercado_pago) if electronic_type == 122 else auth_code,
                approved_amount=amount,
                id_auth=authorizer_name,
                cnpj_auth=authorizer_cnpj,
                bandeira=card_type,
                receipt_merchant=base64.b64encode(receipt_merchant or receipt_customer),
                receipt_customer=base64.b64encode(receipt_customer),
                expiration_date=expiration_date,
                nsu=nsu,
                transaction_processor=transaction_processor,
                last_digits=last_digits,
                sat_acquirer=sat_acquirer,
                nfce_brand=nfce_brand,
                original_nfce_brand=nfce_brand,
                nsu_host=nsu_host,
                electronic_type=tender_id,
                transaction_identifier=transaction_identifier
            )
        else:
            if voided:
                return PaymentResponseMessage("Cancelado")

            if api_ret == API_NO_SITEF_COMMUNICATION:
                self.service_finder.sitef_unavailable(ip_sitef)

            return _format_api_error_response(api_ret)

    def _must_ignore_command(self, field_type):
        return self._is_show_qrcode_command(field_type)

    @staticmethod
    def _is_show_qrcode_command(field_type):
        return field_type.value == SitefParams.QR_CODE

    def _log_api_config_ret_error(self, api_ret):
        error_codes = {
            0: "No Error",
            1: "IP Error",
            2: "Store Code Error",
            3: "Terminal Code Error",
            6: "TCP/IP Error",
            7: "Memory Error",
            8: "CliSiTef Error",
            10: "CliSiTef Access Error"
        }
        self.logger.error("API Config Error: {}".format(error_codes.get(api_ret, "Unknown")))

    def _log_api_start_ret_error(self, api_ret):
        self.logger.warning("API Start Error: {}".format(api_ret))

    @staticmethod
    def _get_coupon_code(
        pos_id,     # type: int
        order_id,   # type: int
        tender_id   # type: str
    ):
        return "{0}{1}{2}".format(pos_id, order_id, tender_id)

    def _check_sitef_is_available(
        self
    ):
        # type: (...) -> None

        self.current_ip = self.service_finder.find_sitef_service() if self.current_ip is None else self.current_ip
        result = self.configura_sitef_interativo(
            str(self.current_ip),
            str(self.store_id),
            _get_terminal_id(self.pdv_tef)
            ,
            0
        )
        if result != API_SUCCESS_OK:
            self.service_finder.sitef_unavailable(self.current_ip)
            self._log_api_config_ret_error(result)
            raise SitefUnavailableException("$SITEF_UNAVAILABLE")

    def _notify_start_process(
        self,
        pos_id,         # type: int
        tender_type     # type: str
    ):
        # type: (...) -> None

        self.message_bus.mbcontext.MB_EasyEvtSend(
            subject="SITEF",
            type="PROCESS_STARTED",
            xml=tender_type,
            sourceid=pos_id,
            synchronous=True
        )

    def _notify_status_update(
        self,
        pos_id,     # type: Any[Union[str, int]]
        data=""     # type: Optional[str]
    ):
        # type: (...) -> None

        data = ";".join([data, pos_id])
        self.message_bus.mbcontext.MB_EasyEvtSend(
            subject="SITEF_STATUS_UPDATE",
            type="STATUS_UPDATE",
            xml=data
        )

    def _notify_end_process(
        self,
        pos_id  # type: int
    ):
        # type: (...) -> None

        self.message_bus.mbcontext.MB_EasyEvtSend(
            subject="SITEF_STATUS_UPDATE_FINISHED",
            type="SITEF_STATUS_UPDATE_FINISHED",
            xml=str(pos_id)
        )

    @staticmethod
    def _idle_field(
        command,        # type: c_long
        field_type,     # type: c_long,
        value           # type: int
    ):
        # type: (...) -> bool

        return command.value == CommandCodes.idle_field and field_type.value == value

    def _wait_action(
        self,
        pos_id,         # type: str
        order_id,       # type: str
        last_time,      # type: int
        output_buffer,  # type: Array[c_char]
        max_size,       # type: c_long
        last_message    # type: str
    ):
        # type: (...) -> float

        if last_time == -1:
            return time.time()

        current_time = time.time()
        if current_time - last_time > self.timeout:
            with self.cancel_lock:
                self.cancel = True

            log_message = "SyncSitefProcessor - Wait Action - OrderId: {0} - IP SiTef: {1}"
            self.logger.debug(log_message.format(order_id, self.current_ip))

            output_buffer.value = "0" * max_size.value
            return last_time

        delimiter = "|" if last_message.startswith("$") else " "
        message = "{}{}{}".format(last_message, delimiter, str(self.timeout - int(current_time - last_time)))
        self._notify_status_update(
            pos_id=pos_id,
            data=message
        )

        return last_time

    def _call_command_function(
        self,
        command,                    # type: c_long
        output_buffer,              # type: Array[c_char]
        key_translate=None,         # type: str
        extra_data_display=None     # type: Optional[List[str]]
    ):
        # type: (...) -> str

        if command.value in [CommandCodes.show_message_operator, CommandCodes.show_message_both]:
            return self.format_message_translate(
                output_buffer=output_buffer,
                key_translate=key_translate,
                extra_data_display=extra_data_display
            )

        if command.value in [CommandCodes.clear_message_operator, CommandCodes.clear_message_both]:
            # TODO: In SITEF here was a call to SitefCallback
            return ""

        return ""

    @staticmethod
    def format_message_translate(
        output_buffer,              # type: Array[c_char]
        key_translate,              # type: Optional[str]
        extra_data_display=None     # type: Optional[List[str]]
    ):
        # type: (...) -> str

        if key_translate is not None and extra_data_display is None:
            return key_translate

        if extra_data_display is None:
            return output_buffer.value

        extra_data_display.insert(0, output_buffer.value)
        message = "|".join([str(e) for e in extra_data_display])

        return "{}|{}".format(key_translate, message)

    @staticmethod
    def _set_response_cancel_data(
        response,       # type: SitefResponse
        field_type,     # type: int
        output_buffer   # type: Array[c_char]
    ):
        # type: (...) -> SitefResponse

        if field_type == FieldCodes.receipt_customer:
            response.customer_receipt = base64.b64encode(output_buffer.value)
        elif field_type == FieldCodes.receipt_merchant:
            response.merchant_receipt = base64.b64encode(output_buffer.value)
        elif field_type == FieldCodes.nsu:
            response.nsu = output_buffer.value
        elif field_type == FieldCodes.nsu_host:
            response.nsu_host = output_buffer.value
        elif field_type == FieldCodes.nsu_origin:
            response.nsu_origin = output_buffer.value
        elif field_type == FieldCodes.transaction_value:
            response.transaction_value = output_buffer.value

        return response
