# -*- coding: utf-8 -*-

import base64
import json
import os
import time

import re

from modelcommon.model import SitefResponse
from pos_model import TenderType

from typing import (
    Any,
    Union,
    Optional,
)

from model import (
    CancelPaymentOrderData,
    ConfirmFinishPaymentData,
    FinishPaymentData,
    PaymentResponseMessage,
    PaymentResponseData,
    TestConnectionData,
    StartPaymentData,
    PaymentResponse,
    TPag,
)
from modelcommon.electronicpayment import TransactionProcessors
from paymentprocessor import PaymentProcessor
from _CapptaCallback import CapptaCallback
from util import get_transaction_identifier

cappta_client = None


class CapptaProcessor(PaymentProcessor):

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

        super(CapptaProcessor, self).__init__(message_bus, dialog_manager, config, logger, cache)
        self.cnpj = config.find_value("CnpjLoja")
        self.pos_id = config.find_value("PosId")
        self.pdv_tef = config.find_value("PdvTef")
        self.auth_key = config.find_value("ChaveAutenticacao")
        self.administrative_password = config.find_value("AdministrativePassword", "cappta")
        self.callback = CapptaCallback(self.dialog_manager)
        self.DetalhesCredito = None
        self.IMensagem = None
        self.IRespostaTransacaoPendente = None
        self.IRequisicaoParametro = None
        self.IRespostaOperacaoAprovada = None
        self.IRespostaOperacaoRecusada = None
        self.cappta_client = self._get_cappta_client(config)
        self.cache = cache

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

        return True

    def add_payment(
        self,
        start_payment_data,     # type: StartPaymentData
    ):
        # type: (...) -> PaymentResponse

        try:
            transaction_identifier = get_transaction_identifier(
                order_id=start_payment_data.order_id,
                tender_id=start_payment_data.tender_id,
            )
            ret = self._authenticate()
            if ret == 0:
                self.cappta_client.IniciarMultiCartoes(9)
                tender_type = start_payment_data.electronic_type

                amount = float(start_payment_data.amount.replace(".", "").replace(",", "."))

                if tender_type == TenderType.credit:
                    ret = self.cappta_client.PagamentoCredito(amount, self.DetalhesCredito())
                elif tender_type in (TenderType.debit, TenderType.voucher):
                    ret = self.cappta_client.PagamentoDebito(amount)

                if ret != 0:
                    self.callback.callback(str(self.pos_id), ret, "")
                    return PaymentResponseMessage("Pagamento interrompido")

                tef_loop_variable = self.cappta_client.IterarOperacaoTef()
                while True:
                    if isinstance(tef_loop_variable, self.IMensagem):
                        self._execute_message_response(
                            tef_loop_variable=tef_loop_variable,
                        )

                    elif isinstance(tef_loop_variable, self.IRespostaTransacaoPendente):
                        ret = self._execute_pending_transaction_response(
                            tef_loop_variable=tef_loop_variable,
                        )

                    elif isinstance(tef_loop_variable, self.IRequisicaoParametro):
                        ret = self._execute_request_parameter(
                            tef_loop_variable=tef_loop_variable,
                        )

                    is_approved_operation = isinstance(tef_loop_variable, self.IRespostaOperacaoAprovada)
                    is_denied_operation = isinstance(tef_loop_variable, self.IRespostaOperacaoRecusada)
                    if is_approved_operation or is_denied_operation:
                        break

                    tef_loop_variable = self.cappta_client.IterarOperacaoTef()

                if isinstance(tef_loop_variable, self.IRespostaOperacaoAprovada):
                    approved_payment = self.IRespostaOperacaoAprovada(tef_loop_variable)
                    response = self._get_payment_response(
                        approved_payment=approved_payment,
                        start_payment_data=start_payment_data,
                        tender_type=tender_type,
                    )
                    response.result = ret
                    response.approved_amount = start_payment_data.amount
                    response.electronic_type = start_payment_data.electronic_type
                    response.transaction_identifier = transaction_identifier

                    msg = "POS %s - PaymentApproved. Order %s" % (str(self.pos_id), str(start_payment_data.order_id))
                    self.logger.debug(msg)
                    return response

                elif isinstance(tef_loop_variable, self.IRespostaOperacaoRecusada):
                    response = self._execute_action_operation_declined(
                        order_id=start_payment_data.order_id,
                        tef_loop_variable=tef_loop_variable,
                    )
                    return PaymentResponseMessage(response)
            else:
                message = "Falha ao autenticar POS %s - Código de erro %s" % (str(self.pos_id), str(ret))
                self.logger.error(message)
                return PaymentResponseMessage(message)
        finally:
            self.dialog_manager.close_dialogs(start_payment_data.pos_id)

    def _get_payment_response(
        self,
        approved_payment,       # type: IRespostaOperacaoAprovada
        start_payment_data,     # type: StartPaymentData
        tender_type,            # type: TenderType
    ):
        # type: (...) -> PaymentResponseData

        customer_slip = approved_payment.CupomLojista
        b64_receipt_merchant = base64.b64encode(str(customer_slip).replace("\"", ""))
        client_slip = approved_payment.CupomCliente
        b64_receipt_customer = base64.b64encode(str(client_slip).replace("\"", ""))
        self._set_cache_receipt_data(
            order_id=start_payment_data.order_id,
            tender_id=start_payment_data.tender_id,
            receipt_customer=b64_receipt_customer,
            receipt_merchant=b64_receipt_merchant,
        )

        return PaymentResponseData(
            media=str(approved_payment.NomeBandeiraCartao),
            result="",
            card_number=str(approved_payment.NumeroCartaoCliente)[:6],
            auth_code=str(approved_payment.CodigoAutorizacaoAdquirente),
            approved_amount="",
            id_auth=approved_payment.NumeroControle,
            cnpj_auth="",
            bandeira=str(approved_payment.CodigoBandeiraCartao),
            receipt_merchant=b64_receipt_merchant,
            receipt_customer=b64_receipt_customer,
            expiration_date="",
            nsu=approved_payment.NsuTef,
            transaction_processor=TransactionProcessors.CAPPTA,
            last_digits=str(approved_payment.NumeroCartaoCliente)[4:],
            sat_acquirer="",
            t_pag=TPag.CREDITO if tender_type == TenderType.credit else TPag.DEBITO,
            t_band=str(approved_payment.CodigoBandeiraCartao),
            nsu_host=approved_payment.NsuAdquirente,
            electronic_type="",
            transaction_identifier="",
        )

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

        try:
            return self.execute_cancel_payment_order(
                cancel_payment_order_data=cancel_payment_order_data,
            )
        finally:
            pos_id = cancel_payment_order_data.pos_id
            self.dialog_manager.close_dialogs(
                pos_id=pos_id,
            )

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

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

        try:
            transaction_identifier = get_transaction_identifier(
                order_id=cancel_payment_order_data.order_id,
                tender_id=cancel_payment_order_data.tender_id,
            )
            result = self._authenticate()
            if result == 0:

                result = self.cappta_client.CancelarPagamento(
                    self.administrative_password,
                    cancel_payment_order_data.id_auth,
                )

                if result == 0:
                    tef_loop_variable = self.cappta_client.IterarOperacaoTef()
                    while True:
                        if isinstance(tef_loop_variable, self.IMensagem):
                            self._execute_message_response(
                                tef_loop_variable=tef_loop_variable,
                                card_number=cancel_payment_order_data.card_number,
                            )

                        elif isinstance(tef_loop_variable, self.IRespostaTransacaoPendente):
                            self._execute_pending_transaction_response(
                                tef_loop_variable=tef_loop_variable,
                            )

                        elif isinstance(tef_loop_variable, self.IRequisicaoParametro):
                            self._execute_request_parameter(
                                tef_loop_variable=tef_loop_variable,
                            )

                        is_approved_operation = isinstance(tef_loop_variable, self.IRespostaOperacaoAprovada)
                        is_denied_operation = isinstance(tef_loop_variable, self.IRespostaOperacaoRecusada)
                        if is_approved_operation or is_denied_operation:
                            break

                        tef_loop_variable = self.cappta_client.IterarOperacaoTef()

                    if isinstance(tef_loop_variable, self.IRespostaOperacaoAprovada):
                        approved_cancel = self.IRespostaOperacaoAprovada(tef_loop_variable)
                        response = self._get_cancel_response(
                            approved_cancel=approved_cancel,
                            cancel_payment_order_data=cancel_payment_order_data,
                        )
                        response.transaction_identifier = transaction_identifier
                        response.transaction_value = cancel_payment_order_data.tender_amount

                        return response

                    elif isinstance(tef_loop_variable, self.IRespostaOperacaoRecusada):
                        response = self._execute_action_operation_declined(
                            order_id=cancel_payment_order_data.order_id,
                            tef_loop_variable=tef_loop_variable,
                        )
                        return PaymentResponseMessage(response)

            return PaymentResponseMessage("$ERROR_CANCEL_ELECTRONIC_PAYMENTS_ORDER")
        except Exception as _:
            self.logger.error("CapptaProcessor Cancel payment order - ERROR")
            return PaymentResponseMessage("$ERROR_CANCEL_ELECTRONIC_PAYMENTS_ORDER")
        finally:
            self.logger.debug("CapptaProcessor Cancel payment order - FINISH")

    def _get_cancel_response(
        self,
        approved_cancel,                # type: IRespostaOperacaoAprovada
        cancel_payment_order_data,      # type: CancelPaymentOrderData
    ):
        # type: (...) -> SitefResponse

        customer_slip = approved_cancel.CupomLojista
        b64_receipt_merchant = base64.b64encode(str(customer_slip).replace("\"", ""))
        client_slip = approved_cancel.CupomCliente
        b64_receipt_customer = base64.b64encode(str(client_slip).replace("\"", ""))
        self._set_cache_receipt_data(
            order_id=cancel_payment_order_data.order_id,
            tender_id=cancel_payment_order_data.tender_id,
            receipt_customer=b64_receipt_customer,
            receipt_merchant=b64_receipt_merchant,
        )
        response = SitefResponse()
        response.customer_receipt = b64_receipt_customer
        response.merchant_receipt = b64_receipt_merchant
        response.nsu = approved_cancel.NsuAdquirente
        response.nsu_host = approved_cancel.NsuTef

        return response

    def _execute_message_response(
        self,
        tef_loop_variable,       # type: IMensagem
        card_number=None,        # type: str
    ):
        # type: (...) -> None

        message = self.IMensagem(tef_loop_variable)
        msg = message.Descricao.encode('utf-8')

        if card_number is not None and 'insira' in msg.lower():
            msg = "$INSERT_CARD_CANCEL_PAYMENT|{}|".format(card_number)

        self.callback.callback(
            pos_id=str(self.pos_id),
            message=msg,
            callback_type="MENSAGEM",
        )
        time.sleep(0.5)

    def _execute_pending_transaction_response(
        self,
        tef_loop_variable,      # type: IRespostaTransacaoPendente
    ):
        # type: (...) -> int

        pending_transactions = self.IRespostaTransacaoPendente(tef_loop_variable)
        msg = pending_transactions.Mensagem.encode('utf-8')
        callback_response = self.callback.callback(
            pos_id=str(self.pos_id),
            message=msg,
            callback_type="REQUISITA_ACAO",
        )
        if callback_response == "CANCELA":
            callback_response = "2"

        return self.cappta_client.EnviarParametro(callback_response, 1)

    def _execute_request_parameter(
        self,
        tef_loop_variable,      # type: IRequisicaoParametro
    ):
        # type: (...) -> int

        request_parameter = self.IRequisicaoParametro(tef_loop_variable)
        request_message = request_parameter.Mensagem.encode('utf-8')

        if 'digite o cartao' in request_message.lower():
            self.dialog_manager.show_message(
                pos_id=str(self.pos_id),
                message="",
            )
            return self.cappta_client.EnviarParametro("", 2)

        if "para cancelar" in request_message.lower():
            request_message = request_message.split("\n")
            request_message = request_message[len(request_message) - 1]
            callback_response = re.findall('[0-9]+', request_message)
            if callback_response:
                return self.cappta_client.EnviarParametro(str(callback_response[0]), 1)

        if '(1-Sim 0-Não)' in request_message:
            request_message = request_message.replace("(1-Sim 0-Não)", "")

        callback_response = self.dialog_manager.show_question(
            pos_id=str(self.pos_id),
            message=request_message,
        )

        callback_response = "1" if callback_response == "0" else "0"

        return self.cappta_client.EnviarParametro(str(callback_response), 1)

    def _execute_action_operation_declined(
        self,
        order_id,           # type: str
        tef_loop_variable,  # type: IRespostaOperacaoRecusada
    ):

        # type: (...) -> str

        operation = self.IRespostaOperacaoRecusada(tef_loop_variable)
        response = operation.Motivo.encode('utf-8')
        log = "POS {0} - Operação recusada. Order: {1}. Motivo: {2}".format(self.pos_id, order_id, response)
        self.logger.debug(log)
        self.callback.callback(
            pos_id=str(self.pos_id),
            message=response,
            callback_type="MENSAGEM",
        )
        time.sleep(0.5)

        return response

    def cancel_payment(self):
        return

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

        try:
            if finish_payment_data.status == 1:
                self.cappta_client.ConfirmarPagamentos()
            else:
                self.cappta_client.DesfazerPagamentos()
            return True
        except Exception as _:
            return False

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

        pass

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

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

        return TransactionProcessors.CAPPTA

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

        pass

    def stop(self):
        return

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

        return self.cache.get_last_receipt(eft_processor=TransactionProcessors.CAPPTA)

    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.CAPPTA,
        )

    def _get_cappta_client(self, config):
        import clr  # Linux (POS Mobile) does not have an implemented library to clr

        global cappta_client
        if cappta_client is not None:
            return cappta_client

        dll_path = self._get_dll_path(config.find_value("DLLPath"))
        old_dir = os.getcwd()
        os.chdir(dll_path)
        clr.AddReference(os.path.join(os.getcwd(), "Cappta.Gp.Api.Com.dll"))

        from Cappta.Gp.Api.Com import ClienteCappta

        cappta_client = ClienteCappta()
        os.chdir(old_dir)

        return cappta_client

    @staticmethod
    def _get_dll_path(dll_path):
        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(dll_path)

    def _authenticate(
        self
    ):
        # type: (...) -> int

        result = self.cappta_client.AutenticarPdv(
            self.cnpj,
            self.pdv_tef,
            self.auth_key,
        )

        if result != 0:
            self.callback.callback(
                pos_id=str(self.pos_id),
                message="$AUTHENTICATION_FAILED",
                callback_type="MENSAGEM",
            )
            time.sleep(3)
            return result

        self.logger.debug("POS %s - Autenticação com sucesso." % (str(self.pos_id)))

        from Cappta.Gp.Api.Com.Model import (
            Configuracoes,
            DetalhesCredito,
            IMensagem,
            IRespostaTransacaoPendente,
            IRequisicaoParametro,
            IRespostaOperacaoAprovada,
            IRespostaOperacaoRecusada,
        )

        self.DetalhesCredito = DetalhesCredito
        self.IMensagem = IMensagem
        self.IRespostaTransacaoPendente = IRespostaTransacaoPendente
        self.IRequisicaoParametro = IRequisicaoParametro
        self.IRespostaOperacaoAprovada = IRespostaOperacaoAprovada
        self.IRespostaOperacaoRecusada = IRespostaOperacaoRecusada

        configs = Configuracoes()
        configs.ExibirInterface = False
        result_config = self.cappta_client.Configurar(configs)
        if result_config != 0:
            self.logger.debug("Não foi possível realizar a configuração")

        return result
