import base64
import json
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 modelcommon.electronicpayment import TransactionProcessors

from messagebus import DataType, DefaultToken, Message
from mbcontextmessagehandler import MbContextMessageBus
from modelcommon.customexception import (
    SitefUnavailableException,
)
from modelcommon.model import SitefResponse
from modelcommon.sitef import (
    PaymentCodes,
    SitefFunctions,
    ResultCodes,
    CommandCodes,
    FieldCodes,
    FinalizeActionFunction,
    CancelCodes,
)
from pos_model import TenderType

from paymentprocessor import PaymentProcessor
from typing import (
    Any,
    List,
    Optional,
    Union,
    Dict,
)
from ._SitefActions import SitefActions
from ._SitefParams import SitefParams
from ._SitefServiceFinder import SitefServiceFinder
from ._SitefTPag import SitefTPag
from dialogmanager import DialogManager
from model import (
    CancelPaymentOrderData,
    ConfirmFinishPaymentData,
    FinishPaymentData,
    PaymentResponse,
    PaymentResponseData,
    PaymentResponseMessage,
    StartPaymentData,
    TestConnectionData,
    TPag,
)
from util import (
    get_transaction_identifier, Cache,
)

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
MINIMUM_TIMEOUT = 15
OPERATOR_CANCEL = -2


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(PaymentProcessor):
    def __init__(
        self,
        message_bus,        # type: MbContextMessageBus
        dialog_manager,     # type: DialogManager
        config,             # type: Group
        logger,             # type: Logger
        cache,              # type: Cache
    ):
        # type: (...) -> None

        super(SitefPaymentProcessor, self).__init__(message_bus, dialog_manager, config, logger, cache)

        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))
        if self.timeout < MINIMUM_TIMEOUT:
            self.timeout = MINIMUM_TIMEOUT
        self.connection_test_interval = int(config.find_value("ConnectionTestInterval", 0))
        self.show_qr_code = config.find_value("ShowQRCode", "False").upper() == "TRUE"
        self.is_sync = config.find_value("IsMobileSynch", "False") == "True"
        self.pdv_tef = int(config.find_value("PdvTef") or config.find_value("PosId"))
        self.pos_id = int(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(
            connection_test_interval=self.connection_test_interval,
            sitef_ip_list=config.find_values("IP") or [],
            store_id=self.store_id,
            pdv_tef=self.pdv_tef,
            logger=self.logger,
            dll_path=dll_path,
        )

        self.current_ip = None
        self.current_order = None

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

        self.close_dialog_commands = (
            SitefActions.CLEAN_OPERATOR_MESSAGE,
            SitefActions.CLEAN_CUSTOMER_MESSAGE,
            SitefActions.CLEAN_OPERATOR_AND_CUSTOMER_MESSAGE,
        )

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

    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,                       # type: SitefPaymentProcessor
        cancel_payment_order_data,  # type: CancelPaymentOrderData
        additional_params="",       # type: Optional[str]
    ):
        # type: (...) -> Any[Union[SitefResponse, str]]

        try:
            self.service_finder.lock_sitef()
            return self.execute_cancel_payment_order(
                cancel_payment_order_data=cancel_payment_order_data,
                additional_params=additional_params,
            )
        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
        additional_params="",       # type: Optional[str]
    ):
        # 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,
            "",
            additional_params
        )

        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 = ""
            menu_title = ""

            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 self.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(cancel_payment_order_data.nsu_host)
                    elif self._idle_field(command, field_type, FieldCodes.cancel_card_number):
                        card_bin = self.dialog_manager.show_input(pos_id, output_buffer.value, input_type="text")
                        if card_bin == "CANCELA" or card_bin == "None":
                            output_buffer.value = "0" * max_size.value
                            self.cancel_payment()
                        else:
                            output_buffer.value = str(card_bin)
                    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_TITLE:
                        menu_title = output_buffer.value
                    elif command.value == SitefActions.CLEAN_MENU_TITLE:
                        menu_title = ""
                    elif command.value == SitefActions.MENU_CONTENT:
                        self._handle_menu_content(
                            pos_id=pos_id,
                            output_buffer=output_buffer,
                            max_size=max_size,
                            menu_title=menu_title,
                            media=cancel_payment_order_data.media,
                            type_code=type_code,
                        )

                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._set_cache_receipt_data(
                    order_id=cancel_payment_order_data.order_id,
                    tender_id=cancel_payment_order_data.tender_id,
                    receipt_customer=response.customer_receipt,
                    receipt_merchant=response.merchant_receipt,
                )
                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"

    def cancel_payment(
        self,   # type: SitefPaymentProcessor
    ):
        # type: (...) -> str

        with self.cancel_lock:
            self.logger.info("[SITEF] Cancel payment called")
            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,
    ):
        # type: (...) -> str

        self.register_eft_processor(
            pos_id=self.pos_id,
            processor_name=TransactionProcessors.SITEF,
        )

        return TransactionProcessors.SITEF

    def get_document(
        self,
        pos_id,     # type: str
    ):
        # type: (...) -> str

        self.logger.debug("SiTEF get doc started")
        try:
            self.service_finder.lock_sitef()
            ip_sitef = self.service_finder.find_sitef_service()

            ret = self.configura_sitef_interativo(
                str(self.current_ip),
                str(self.store_id),
                _get_terminal_id(self.pdv_tef),
                0
            )

            if ret != 0:
                self.service_finder.sitef_unavailable(ip_sitef)
                raise Exception("Servidor SiTEF Inoperante")

            fiscal_date = str(datetime.now().date())
            fiscal_time = str(datetime.now().time())

            api_ret = self.inicia_sitef_interativo(789, "", '{}'.format(pos_id), fiscal_date, fiscal_time, "", "")

            if api_ret != 10000:
                raise Exception("Erro ao iniciar SiTEF")

            voided = False
            last_time = -1
            command = c_long(0)
            field_type = c_long(0)
            tam_min = c_long(0)
            tam_max = c_long(0)
            tam_buffer = 32768
            output_buffer = create_string_buffer('\000' * tam_buffer)

            while api_ret == API_LOOP_OK:
                api_ret = self.continua_sitef_interativo(
                    addressof(command), addressof(field_type),
                    addressof(tam_min), addressof(tam_max), output_buffer,
                    tam_buffer, 0
                )

                if api_ret != API_LOOP_OK:
                    break

                if not command.value == 0:
                    if command.value == SitefActions.REQUEST_USER_INFO_WITH_LIMITS:
                        if field_type.value == SitefParams.MESSAGE_CODE:
                            output_buffer.value = "7"

                        elif field_type.value == SitefParams.MIN_SIZE:
                            output_buffer.value = "11"

                        elif field_type.value == SitefParams.MAX_SIZE:
                            output_buffer.value = "11"

                        elif field_type.value == SitefParams.TIME_OUT:
                            output_buffer.value = "0"

                        if last_time == -1:
                            last_time = time.time()
                        else:
                            current_time = time.time()
                            if current_time - last_time > self.timeout:
                                with self.get_documents_lock:
                                    voided = True

                                self.logger.debug(
                                    "SintefGetDoc - Cancel Requested - IP SiTef: {}".format(ip_sitef)
                                )
                                output_buffer.value = "0" * tam_max.value
                    elif command.value == SitefActions.SHOW_CUSTOM_MESSAGE:
                        self.dialog_manager.show_message(pos_id, "$REQUEST_DOCUMENT_ON_PINPAD")
                    elif command.value in self.close_dialog_commands:
                        self.dialog_manager.close_dialogs(pos_id)
                    elif command.value == SitefActions.CANCEL_OPERATION:
                        if field_type.value != SitefParams.CANCEL_OPERATION_REASON:
                            voided = True
                        output_buffer.value = "0"
                        time.sleep(1.5)
                    else:
                        self.dialog_manager.show_message(
                            pos_id, "Mensagem {} {}".format(command.value, output_buffer.value)
                        )
                        last_time = -1


            if api_ret == 0:
                self.finaliza_sitef_interativo("0", '{}'.format(pos_id), fiscal_date, fiscal_time, None)
                self.logger.debug("SiTEF get doc ended")
                return output_buffer.value
            else:
                if voided:
                    raise Exception("Cancelado")

                if api_ret == -5:
                    self.service_finder.sitef_unavailable(ip_sitef)
                    raise Exception("Erro de comunicacao no PinPad: {}".format(api_ret))
                else:
                    raise Exception("Erro na chamada do PinPad: {}".format(api_ret))

        except Exception as ex:
            self.logger.exception("Erro no acesso ao PinPad: {}".format(ex))

        finally:
            self.service_finder.release_sitef()

    def get_last_receipt(
        self,
        eft_processor,      # type: str
    ):
        # type: (...) -> Optional[str]

        return self.cache.get_last_receipt(eft_processor=TransactionProcessors.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,                    # type: SitefPaymentProcessor
        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

        payment_code = SitefFunctions.get_payment_code(electronic_type=electronic_type)
        is_digital_wallets = payment_code == PaymentCodes.digital_wallet

        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)
        api_ret = self.inicia_sitef_interativo(
            payment_code,
            amount,
            transaction_identifier,
            fiscal_date,
            fiscal_time,
            operator,
            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
        receipt_merchant = None
        receipt_customer = None
        id_mercado_pago = None
        menu_title = ""
        last_digits = "0000"
        sat_acquirer = ""
        t_band = ""

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

            log_message = "API RET {0} - COMMAND TYPE: {1} - FIELD TYPE {2} - BUFFER: {3}"
            self.logger.debug(log_message.format(str(api_ret), command.value, field_type.value, output_buffer.value))
            if api_ret != API_LOOP_OK:
                break

            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:
                    input_type = "text" if sitef_loop_data.electronic_type == TenderType.gift_card else "number"
                    pin = self.dialog_manager.show_input(pos_id, output_buffer.value, input_type=input_type)
                    if pin == "CANCELA" or pin == "None":
                        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)
                    if str(selected_choice) == "-1":
                        self.cancel_payment()
                    else:
                        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))

            self.logger.debug("FIELD TYPE: {0} - VALUE: {1}".format(field_type.value, output_buffer.value))
            if field_type.value == SitefParams.CARD_BIN:
                card_bin = 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:
                t_band = output_buffer.value

        if api_ret == API_LOOP_FINISHED and auth_code is not None:
            self.logger.debug("SiTEF loop finished")

            receipt_customer = self._encode_receipt(
                receipt=receipt_customer,
            )
            receipt_merchant = self._encode_receipt(
                receipt=receipt_merchant,
            )

            self._set_cache_receipt_data(
                order_id=order_id,
                tender_id=tender_id,
                receipt_customer=receipt_customer,
                receipt_merchant=receipt_merchant,
            )

            t_pag = SitefTPag().get_t_pag(
                card_type=card_type,
                payment_code=payment_code,
                electronic_type=electronic_type,
            )

            return PaymentResponseData(
                media=institution_name.strip(),
                result=api_ret,
                card_number=card_bin,
                auth_code="{}{}".format(transaction_date, id_mercado_pago) if is_digital_wallets else auth_code,
                approved_amount=amount,
                id_auth=authorizer_name,
                cnpj_auth=authorizer_cnpj,
                bandeira=card_type,
                receipt_merchant=receipt_merchant,
                receipt_customer=receipt_customer,
                expiration_date=expiration_date,
                nsu=nsu,
                transaction_processor=TransactionProcessors.SITEF,
                last_digits=last_digits,
                sat_acquirer=sat_acquirer,
                t_pag=t_pag,
                t_band=t_band,
                nsu_host=nsu_host,
                electronic_type=tender_id,
                transaction_identifier=transaction_identifier
            )

        if voided:
            return PaymentResponseMessage("Cancelado")

        if api_ret == OPERATOR_CANCEL:
            self.logger.info("[SITEF] Operator cancel transaction")
            self._cancel_pending_transaction(
                pos_id=pos_id,
                order_id=order_id,
                transaction_identifier=transaction_identifier,
                fiscal_date=fiscal_date,
                fiscal_time=fiscal_time,
            )
            self.logger.info("[SITEF] End process cancel transaction")

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

        return _format_api_error_response(api_ret)

    def _encode_receipt(
        self,
        receipt,    # type: str
    ):
        # type: (...) -> str

        try:
            receipt = base64.b64encode(receipt)
        except Exception as _:
            self.logger.exception("Error encoding receipt: {}".format(receipt))
            receipt = ''

        return receipt

    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 ""

    def _handle_menu_content(
        self,           # type: SitefPaymentProcessor
        pos_id,         # type: str
        output_buffer,  # type: Array[c_char]
        max_size,       # type: c_long
        menu_title,     # type: str
        media,          # type: str
        type_code,      # type: int
    ):
        # type: (...) -> None

        choices = self._convert_output_buffer_to_choices(options=output_buffer.value)

        if type_code == CancelCodes.digital_wallet:
            for key, value in choices.items():
                if value == media:
                    output_buffer.value = key
                    return

        selected_choice = self._show_choice_options(
            pos_id=pos_id,
            menu_title=menu_title,
            choices=choices,
        )

        if selected_choice is None:
            output_buffer.value = "0" * max_size.value
            self.cancel_payment()
            return

        output_buffer.value = selected_choice

    @staticmethod
    def _convert_output_buffer_to_choices(
        options,    # type: str
    ):
        # type: (...) -> Dict

        options = options.split(';')

        return {option.split(':')[0]: option.split(':')[1] for option in options if option}

    def _show_choice_options(
        self,           # type: SitefPaymentProcessor
        pos_id,         # type: str
        menu_title,     # type: str
        choices,        # type: Dict
    ):
        # type: (...) -> Optional[str]

        selected_choice = self.dialog_manager.show_choices(
            pos_id=pos_id,
            choices=choices,
            message=menu_title,
        )

        if selected_choice == "-1":
            return None

        return selected_choice

    @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

    def _set_cache_receipt_data(
        self,
        order_id,           # type: str
        tender_id,          # type: str
        receipt_customer,   # type: str
        receipt_merchant,   # type: str
    ):
        # type: (...) -> None

        receipt_data = {
            "order_id": order_id,
            "tender_id": tender_id,
            "receipt_customer": receipt_customer,
            "receipt_merchant": receipt_merchant,
        }
        receipt_data = json.dumps(receipt_data)
        self.cache.set_last_receipt(
            receipt_data=receipt_data,
            eft_processor=TransactionProcessors.SITEF,
        )

    def _cancel_pending_transaction(
        self,                       # type: SitefPaymentProcessor
        pos_id,                     # type: str
        order_id,                   # type: str
        transaction_identifier,     # type: str
        fiscal_date,                # type: str
        fiscal_time,                # type: str
    ):
        # type: (...) -> None

        message = "[SITEF] Cancel pending transaction. Pos id: {} - Order Id: {} - Transaction: {}"
        self.logger.info(message, pos_id, order_id, transaction_identifier)

        finish_payment_data = FinishPaymentData(
            pos_id=pos_id,
            order_id=order_id,
            fiscal_date=fiscal_date,
            fiscal_time=fiscal_time,
            status="0",
            tender_details="{}",
        )
        self.finish_payment(finish_payment_data=finish_payment_data)

        message = "[SITEF] Finish cancel pending transaction. Pos id: {} - Order Id: {} - Transaction: {}"
        self.logger.info(message, pos_id, order_id, transaction_identifier)

    def get_qr_code_config(
        self,   # type: SitefPaymentProcessor
    ):
        # type: (...) -> bool

        return self.show_qr_code

