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

import base64
import bisect
import glob
import logging
import os
import os.path
import re
import shutil
import zipfile
import zlib
from datetime import datetime, timedelta
from decimal import Decimal as D
from xml.etree import cElementTree as eTree

from helper import remove_accents

import fiscalpersistence
import printer
from bustoken import (
    TK_FISCALWRAPPER_CANCEL_ORDER,
    TK_FISCALWRAPPER_CHANGE_CONTINGENCY_STATUS,
    TK_FISCALWRAPPER_EXPORT_FISCAL_FILES,
    TK_FISCALWRAPPER_GET_BASE_REDUCTION,
    TK_FISCALWRAPPER_GET_CERTIFICATE_EXPIRATION_DAYS,
    TK_FISCALWRAPPER_GET_CONTINGENCY_STATUS,
    TK_FISCALWRAPPER_GET_CST,
    TK_FISCALWRAPPER_GET_FISCAL_XML,
    TK_FISCALWRAPPER_GET_IBPT_TAX,
    TK_FISCALWRAPPER_GET_LAST_ORDER_ID,
    TK_FISCALWRAPPER_GET_NF_TYPE,
    TK_FISCALWRAPPER_GET_ORDERS_DATA,
    TK_FISCALWRAPPER_GET_TIP,
    TK_FISCALWRAPPER_PRINT_DONATION_COUPON,
    TK_FISCALWRAPPER_PROCESS_REQUEST,
    TK_FISCALWRAPPER_REPRINT,
    TK_FISCALWRAPPER_RE_SIGN_XML,
    TK_FISCALWRAPPER_SAT_OPERATIONAL_STATUS_REQUEST,
    TK_FISCALWRAPPER_SAT_PAYMENT_STATUS,
    TK_FISCALWRAPPER_SAT_PROCESS_PAYMENT,
    TK_FISCALWRAPPER_SAT_STORE_STATUS,
    TK_FISCALWRAPPER_SEARCH_REQUEST,
    TK_FISCALWRAPPER_SEFAZ_CONNECTIVITY,
    TK_FISCALWRAPPER_SITUATION,
    TK_SAT_OPERATIONAL_STATUS_REQUEST,
    TK_SAT_PROCESS_PAYMENT,
    TK_SAT_PAYMENT_STATUS,
    TK_SAT_STATUS_REQUEST,
)
from typing import (
    Dict,
    List,
    Optional,
    Tuple,
    Union,
)

from common import FiscalParameterController
from comp_exceptions import EmptySearchException, VoidOrderException
from fiscalinterface import FiscalProcessor
from fiscalpaymentparser import convert_order_tenders_to_fiscal_tenders
from fiscalpersistence import FiscalDataRepository
from old_helper import OrderTaker, round_half_away_from_zero, remove_xml_namespace, \
    convert_from_utf_to_localtime, regex_search
from ibpttaxprocessor import IbptTaxProcessor
from messagehandler import EventHandler
from msgbus import TK_SYS_NAK, TK_SYS_ACK, MBEasyContext, MBException, FM_PARAM, MBMessage
from nfce import NfceRequestBuilder, NfceConnectivityTester, NfceSituationChecker, NfceContingencia, NfceProcessor
from nfcecanceler import NfceCanceler
from nfcedisabler import OrderDisabler
from order_retriever import OrderRetrieverService
from persistence import Driver as DBDriver
from printer import DonationCouponPrinter
from sat import SatProcessor, SatServiceFinder
from services import (
    SequencerService,
    OrderService,
)
from sysactions import (
    get_model,
    get_posot,
    translate_message,
)
from systools import sys_log_exception

from models import NfceCstatCode

logger = logging.getLogger("FiscalWrapper")
logger_coupons = logging.getLogger("FiscalWrapperCoupons")
logger_canceler = logging.getLogger("FiscalWrapperNfceCanceler")


class FiscalWrapperEventHandler(EventHandler):
    def __init__(self, mbcontext, fiscal_processor, printers, nfce_connectivity_tester, nfce_situation_checker, order_disabler, nfce_canceler, fiscal_parameter_controller, xml_enviados, xml_erros, export_xml_path, fiscal_sent_dir, nf_type, versao_ws, ibpt_tax_processor, order_retriever_service, xml_inconsistentes, donation_coupon_printer, nfce_contingencia, period_to_retry_orders_with_exception, synchronous_mode, sequencer_service):
        # type: (MBEasyContext, Union[FiscalProcessor, NfceProcessor, SatProcessor], Dict[str, printer.FiscalPrinter], NfceConnectivityTester, NfceSituationChecker, OrderDisabler, NfceCanceler, FiscalParameterController, str, str, str, str, str, int, IbptTaxProcessor, OrderRetrieverService, str, DonationCouponPrinter, NfceContingencia, str, bool, SequencerService) -> None
        super(FiscalWrapperEventHandler, self).__init__(mbcontext)
        self.fiscal_processor = fiscal_processor
        self.printers = printers
        self.nf_type = nf_type  # type: str
        self.nfce_connectivity_tester = nfce_connectivity_tester
        self.nfce_situation_checker = nfce_situation_checker
        self.order_disabler = order_disabler
        self.order_retriever_service = order_retriever_service
        self.xml_enviados = xml_enviados
        self.xml_erros = xml_erros
        self.export_xml_path = export_xml_path
        self.fiscal_sent_dir = fiscal_sent_dir
        self.nfce_canceler = nfce_canceler
        self.versao_ws = versao_ws
        self.fiscal_parameter_controller = fiscal_parameter_controller
        self.ibpt_tax_processor = ibpt_tax_processor
        self.xml_inconsistentes = xml_inconsistentes
        self.donation_coupon_printer = donation_coupon_printer
        self.nfce_contingencia = nfce_contingencia
        self.period_to_retry_orders_with_exception = period_to_retry_orders_with_exception
        self.synchronous_mode = synchronous_mode
        self.sequencer_service = sequencer_service
        self.order_service = OrderService()

    def get_handled_tokens(self):
        return [
            TK_FISCALWRAPPER_CANCEL_ORDER,
            TK_FISCALWRAPPER_CHANGE_CONTINGENCY_STATUS,
            TK_FISCALWRAPPER_EXPORT_FISCAL_FILES,
            TK_FISCALWRAPPER_GET_BASE_REDUCTION,
            TK_FISCALWRAPPER_GET_CERTIFICATE_EXPIRATION_DAYS,
            TK_FISCALWRAPPER_GET_CONTINGENCY_STATUS,
            TK_FISCALWRAPPER_GET_CST,
            TK_FISCALWRAPPER_GET_FISCAL_XML,
            TK_FISCALWRAPPER_GET_IBPT_TAX,
            TK_FISCALWRAPPER_GET_LAST_ORDER_ID,
            TK_FISCALWRAPPER_GET_NF_TYPE,
            TK_FISCALWRAPPER_GET_ORDERS_DATA,
            TK_FISCALWRAPPER_GET_TIP,
            TK_FISCALWRAPPER_PRINT_DONATION_COUPON,
            TK_FISCALWRAPPER_PROCESS_REQUEST,
            TK_FISCALWRAPPER_REPRINT,
            TK_FISCALWRAPPER_RE_SIGN_XML,
            TK_FISCALWRAPPER_SAT_OPERATIONAL_STATUS_REQUEST,
            TK_FISCALWRAPPER_SAT_PAYMENT_STATUS,
            TK_FISCALWRAPPER_SAT_PROCESS_PAYMENT,
            TK_FISCALWRAPPER_SAT_STORE_STATUS,
            TK_FISCALWRAPPER_SEARCH_REQUEST,
            TK_FISCALWRAPPER_SEFAZ_CONNECTIVITY,
            TK_FISCALWRAPPER_SITUATION,
            TK_SAT_OPERATIONAL_STATUS_REQUEST,
            TK_SAT_PAYMENT_STATUS,
            TK_SAT_PROCESS_PAYMENT,
        ]

    def handle_message(self, msg):
        # type: (MBMessage) -> Union[None, bool]
        try:
            if msg.token == TK_FISCALWRAPPER_PROCESS_REQUEST:
                response = self._handle_process_request(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_CERTIFICATE_EXPIRATION_DAYS:
                response = self._handle_get_certificate_expiration()
            elif msg.token == TK_FISCALWRAPPER_REPRINT:
                response = self._handle_process_request(msg, reprint=True)
            elif msg.token == TK_FISCALWRAPPER_SEARCH_REQUEST:
                response = self._handle_search_request()
            elif msg.token == TK_FISCALWRAPPER_SEFAZ_CONNECTIVITY:
                response = self._handle_sefaz_connectivity_request()
            elif msg.token == TK_FISCALWRAPPER_SAT_OPERATIONAL_STATUS_REQUEST:
                response = self._handle_sat_op_status_request(msg)
            elif msg.token == TK_FISCALWRAPPER_EXPORT_FISCAL_FILES:
                response = self._export_fiscal_files(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_NF_TYPE:
                response = self._handle_get_nftype(msg)
            elif msg.token == TK_FISCALWRAPPER_SAT_PROCESS_PAYMENT:
                response = self._handle_sat_process_payment_request(msg)
            elif msg.token == TK_FISCALWRAPPER_SAT_PAYMENT_STATUS:
                response = self._handle_sat_payment_status_request(msg)
            elif msg.token == TK_FISCALWRAPPER_CANCEL_ORDER:
                response = self._handle_cancel_order(msg)
            elif msg.token == TK_FISCALWRAPPER_RE_SIGN_XML:
                response = self._handle_re_sign_xml()
            elif msg.token == TK_FISCALWRAPPER_SITUATION:
                response = self._handle_sefaz_situation(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_FISCAL_XML:
                response = self._handle_fiscal_xml_request(msg.data, True)
            elif msg.token == TK_FISCALWRAPPER_GET_CST:
                response = self._handle_get_csts(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_BASE_REDUCTION:
                response = self._handle_get_base_reductions(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_TIP:
                response = self._handle_get_tip_params(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_IBPT_TAX:
                response = self._handle_get_ibpt_tax(msg)
            elif msg.token == TK_FISCALWRAPPER_PRINT_DONATION_COUPON:
                response = self._handle_print_donation_coupon(msg)
            elif msg.token == TK_FISCALWRAPPER_GET_CONTINGENCY_STATUS:
                response = self._handle_get_contingency_status(msg)
            elif msg.token == TK_FISCALWRAPPER_CHANGE_CONTINGENCY_STATUS:
                response = self._handle_change_contingency_status(msg)
            elif msg.token == TK_FISCALWRAPPER_SAT_STORE_STATUS:
                response = self._handle_sat_status_request()
            elif msg.token == TK_FISCALWRAPPER_GET_ORDERS_DATA:
                try:
                    orders_data_list_str = self.order_retriever_service.get_orders_data(msg.data)

                    msg.token = TK_SYS_ACK
                    self.mbcontext.MB_ReplyMessage(msg, data=orders_data_list_str)
                    return
                except Exception as ex:
                    logger.exception("Error handling TK_FISCALWRAPPER_GET_ORDERS_DATA")
                    msg.token = TK_SYS_NAK
                    self.mbcontext.MB_ReplyMessage(msg, data=ex.message)
                    return
            elif msg.token == TK_FISCALWRAPPER_GET_LAST_ORDER_ID:
                try:
                    last_order_id = self.order_retriever_service.get_last_order_id()

                    msg.token = TK_SYS_ACK
                    self.mbcontext.MB_ReplyMessage(msg, data=last_order_id)
                    return
                except Exception as ex:
                    logger.exception("Error handling TK_FISCALWRAPPER_GET_LAST_ORDER_ID")
                    msg.token = TK_SYS_NAK
                    self.mbcontext.MB_ReplyMessage(msg, data=ex.message)
                    return
            else:
                response = (False, "")

            self._log_response_if_error(response=response)

            msg.token = TK_SYS_ACK if response[0] else TK_SYS_NAK
            self.mbcontext.MB_ReplyMessage(msg, data=response[1])

        except Exception as ex:
            sys_log_exception("Unexpected exception when handling event {0}. Error message: {1}".format(msg.token, ex.message))
            msg.token = TK_SYS_NAK
            self.mbcontext.MB_ReplyMessage(msg, data=str(ex))
        except:
            sys_log_exception("Unhandled BaseException Message")
            msg.token = TK_SYS_NAK
            self.mbcontext.MB_ReplyMessage(msg, data=str("Unhandled BaseException"))

        return False

    @staticmethod
    def _log_response_if_error(
        response,   # type: Tuple[bool, str]
    ):
        # type: (...) -> None

        if len(response) == 2:
            response_status = response[0]
            response_message = response[1]

            if response_status is False:
                logger.error(response_message)

    def terminate_event(self):
        self.fiscal_processor.terminate()

    def _handle_get_nftype(self, _):
        # type: (MBMessage) -> (str, str)
        return True, self.nf_type

    def _handle_get_certificate_expiration(self):
        if self.nf_type == "NFCE":
            ret = self.fiscal_processor.do_validation(get_days_to_expiration=True)
            if not ret[0]:
                return False, ret[1]
            return True, ret[1]

    def _handle_process_request(self, msg, reprint=False):
        # type: (MBMessage, Optional[bool]) -> (str, str)

        ret = self.fiscal_processor.do_validation()
        if not ret[0]:
            return False, '\0'.join(map(str, (False, str(ret[1]))))

        data = msg.data.split('\0')
        posid = data[0]
        orderid = data[1] if len(data) > 1 else ""
        danfe_type = data[2] if len(data) > 2 else ""
        model = get_model(posid)
        pos_ot = get_posot(model)
        optional_fields = {
            "fields_to_show": {
                "SaleLine": ["customParams"]
            },
        }
        order = eTree.XML(
            pos_ot.orderPicture(
                posid=posid,
                orderid=orderid,
                optional_fields=optional_fields,
            )
        )
        order = order.find("Order") if order.find("Order") else order
        if not orderid:
            orderid = order.get("orderId")

        if not reprint:
            fiscalization_date = order.find("CustomOrderProperties/OrderProperty[@key='FISCALIZATION_DATE']")
            if fiscalization_date is not None:
                fiscal_xml = order.find("CustomOrderProperties/OrderProperty[@key='FISCAL_XML']")
                if fiscal_xml:
                    return True, ""

                response = self._handle_fiscal_xml_request(orderid, True)
                has_fiscal_xml = response[0]
                if has_fiscal_xml:
                    fiscal_xml = response[0]
                    with FiscalDataRepository(self.mbcontext) as fiscal_repository:
                        fiscal_repository.set_fiscal_xml_custom_property(
                            pos_id=posid,
                            xml_request=fiscal_xml,
                            order_id=orderid,
                            key='FISCAL_XML'
                        )
                    return True, ""

        not_allowed_max_value = self.fiscal_parameter_controller.validate_fiscal_max_value(
            model=model,
            order=order,
        )

        if not_allowed_max_value:
            return False, '\0'.join(map(str, (False, not_allowed_max_value)))

        with FiscalDataRepository(self.mbcontext) as fiscal_repository:  # type: fiscalpersistence.FiscalDataRepository
            fiscal_ok = False
            try:
                fiscal_id_property = "FISCAL_ID"
                fiscal_id_node = order.find("CustomOrderProperties/OrderProperty[@key='{}']".format(fiscal_id_property))

                if fiscal_id_node is not None:
                    fiscal_id_node = fiscal_id_node.get("value")

                if not fiscal_id_node:
                    fiscal_id = self.sequencer_service.build_next_fiscal_id()
                    fiscal_id_node = fiscal_id
                    self.order_service.set_custom_property(
                        pos_id=posid,
                        key=fiscal_id_property,
                        value=str(fiscal_id),
                        pos_ot=pos_ot,
                        blk_notify=True,
                    )

                tenders = convert_order_tenders_to_fiscal_tenders(order)

                customer_name = order.find("CustomOrderProperties/OrderProperty[@key='CUSTOMER_NAME']")
                if customer_name is not None:
                    customer_name = customer_name.get("value")

                total_taxes = self.ibpt_tax_processor.ibpt_tax_calculator(order)
                if reprint:
                    if self.nf_type not in ("SAT", "MFE"):
                        nfce_fiscal_processor = self.fiscal_processor  # type: NfceProcessor
                        params = {
                            'tenders': tenders,
                            'order': order,
                            'customer_name': customer_name,
                            'url_sefaz': nfce_fiscal_processor.url_sefaz,
                            'posid': posid,
                            'total_taxes': total_taxes,
                        }
                    else:
                        params = {
                            'tenders': tenders,
                            'order': order,
                            'customer_name': customer_name,
                            'posid': posid,
                            'total_taxes': total_taxes,
                        }
                    report = self._reprint_sat_or_nfce(fiscal_repository.get_xml_request(orderid), fiscal_repository.get_xml_response(orderid), params, danfe_type)
                    if report[0]:
                        return True, report[1]
                    else:
                        return False, report[1]

                fiscal_data = self.fiscal_processor.request_fiscal(
                        posid,
                        order,
                        tenders,
                        self.nf_type == "PAF",
                        str(fiscal_id_node)
                )
                fiscal_ok = True

                if self.nf_type in ("SAT", "MFE"):
                    sat_fiscal_processor = self.fiscal_processor  # type: SatProcessor
                    sat_used = sat_fiscal_processor.last_sat_used[posid]
                    sat_used = sat_used[3:]
                    response_xml = eTree.tostring(fiscal_data.find("FiscalData"))
                    base64_response = base64.b64encode(response_xml)
                    fiscal_repository.start_save_fiscal_data_thread(posid, order, fiscal_data.find("XmlRequest").text.strip(), sat_used, 1, base64_response)
                    if danfe_type in self.printers:
                        selected_printer = self.printers[danfe_type]
                    else:
                        selected_printer = self.printers[""]

                    report = selected_printer.print_fiscal(
                        pos_id=posid,
                        order=order,
                        tenders=tenders,
                        fiscal_request=fiscal_data.find("XmlRequest"),
                        fiscal_response=fiscal_data.find("FiscalData"),
                        total_taxes=total_taxes,
                    )
                else:
                    request_xml = eTree.XML(fiscal_data[0])
                    response_xml = None
                    xml_response_base64 = None
                    if fiscal_data[1] is not None:
                        response_xml = eTree.XML(fiscal_data[1])
                        xml_response_base64 = base64.b64encode(fiscal_data[1])
                    idx_start = fiscal_data[0].find("<NFe")
                    idx_final = int(str(fiscal_data[0].find("</NFe"))) + 6
                    xml_string = fiscal_data[0][idx_start:idx_final]

                    contingencia = request_xml.find(
                        "{{{0}}}Body/{{{1}}}nfeDadosMsg/{{{2}}}enviNFe/{{{2}}}NFe/{{{2}}}infNFe/{{{2}}}ide/{{{2}}}tpEmis".format(
                            NfceRequestBuilder.NAMESPACE_SOAP,
                            NfceRequestBuilder.NAMESPACE_AUTORIZACAO if self.versao_ws in (1, 3) else NfceRequestBuilder.NAMESPACE_AUTORIZACAO_4,
                            NfceRequestBuilder.NAMESPACE_NFE)).text
                    if not contingencia == "9":
                        xml_string = fiscal_data[2]
                    xml_base64 = base64.b64encode(xml_string)

                    fiscal_repository.start_save_fiscal_data_thread(
                        pos_id=posid,
                        order=order,
                        xml_request=xml_base64,
                        numero_sat="00",
                        sent_to_nfce=0 if contingencia == "9" else 1,
                        xml_response=xml_response_base64,
                    )

                    if danfe_type in self.printers:
                        report = self.printers[danfe_type].print_fiscal(
                            pos_id=posid,
                            order=order,
                            tenders=tenders,
                            fiscal_request=request_xml,
                            fiscal_response=response_xml,
                            total_taxes=total_taxes,
                        )
                    else:
                        report = ""

                self._print_coupon(report)

                return True, report

            except VoidOrderException as ex:
                fiscal_ok = False
                return False, '\0'.join(map(str, (fiscal_ok, str(ex))))

            except MBException:
                logger.exception("Erro de Impressão")
                return False, '\0'.join(map(str, (fiscal_ok, "Verifique a Impressora de Cupom Fiscal")))

            except Exception as ex:
                logger.exception("Nota Rejeitada pela SEFAZ")
                return False, '\0'.join(map(str, (fiscal_ok, str(ex))))

    def _print_coupon(self, report):
        try:
            logger_coupons.info("\n\n***************************************************************\n\n")
            logger_coupons.info(report)
        except Exception as _:
            logger_coupons.info("Error logging coupon")
        finally:
            logger_coupons.info("\n\n***************************************************************\n\n\n\n")

    def _handle_sefaz_connectivity_request(self):
        if self.nf_type == "NFCE":
            self.nfce_connectivity_tester.logger = logger
            if self.nfce_connectivity_tester.test_connectivity():
                return True, "Conexao com Sefaz OK!"
            else:
                return False, "Nao foi possivel se conectar com a Sefaz."
        else:
            return False, "Loja nao opera com NFCE"

    def _handle_sefaz_situation(self, msg):
        if self.nf_type == "NFCE":
            initial_date, final_date = msg.data.split('\0')
            initial_date = initial_date[:4] + '-' + initial_date[4:6] + '-' + initial_date[6:]
            final_date = final_date[:4] + '-' + final_date[4:6] + '-' + final_date[6:]

            if self.versao_ws == 1:
                namespace_consulta_situacao = NfceRequestBuilder.NAMESPACE_CONSULTA_SITUACAO
                tag_consulta_nf_result = "nfeConsultaNF2Result"
            elif self.versao_ws == 3:
                namespace_consulta_situacao = NfceRequestBuilder.NAMESPACE_CONSULTA_SITUACAO_3
                tag_consulta_nf_result = "nfeConsultaNFResult"
            else:
                namespace_consulta_situacao = NfceRequestBuilder.NAMESPACE_CONSULTA_SITUACAO_4
                tag_consulta_nf_result = "nfeResultMsg"

            with FiscalDataRepository(self.mbcontext) as fiscalrepository:
                logger.info("Tratando Evento Consultar Situacao XML")
                conn = None
                try:
                    conn = DBDriver().open(self.mbcontext, service_name="FiscalPersistence")
                    offset = 0
                    while True:
                        sql = """SELECT PosId, OrderId, XMLRequest, OrderPicture
                                  FROM fiscal.FiscalData
                                  WHERE date(DataNota, 'unixepoch', 'localtime') >= '%s'
                                  AND date(DataNota, 'unixepoch', 'localtime') <= '%s'
                                  ORDER BY OrderId ASC LIMIT 500 OFFSET %s""" % (initial_date, final_date, offset)
                        cursor = conn.select(sql)
                        if cursor.rows() == 0:
                            break
                        offset += cursor.rows()
                        eTree.register_namespace('', NfceRequestBuilder.NAMESPACE_NFE)
                        for row in cursor:
                            pos_id, order_id, request, order_picture = map(row.get_entry, ("PosId", "OrderId", "XMLRequest", "OrderPicture"))
                            logger.info("Order %s " % order_id)

                            request_str = base64.b64decode(request + "=" * ((4 - len(request) % 4) % 4))
                            req = eTree.XML(request_str)

                            c_stat = req.find("{{{0}}}protNFe/{{{0}}}infProt/{{{0}}}cStat".format(NfceRequestBuilder.NAMESPACE_NFE))
                            if c_stat is not None:
                                c_stat = c_stat.text

                            if c_stat in ("100", "150"):
                                logger.info("Order %s ja estava com status 100" % order_id)
                                continue
                            try:
                                logger.info("Buscando Protocolo. Order %s " % order_id)

                                nfe = req.find("{{{0}}}NFe/{{{0}}}infNFe".format(NfceRequestBuilder.NAMESPACE_NFE))
                                nfe_key = nfe.attrib["Id"]
                                nfe = nfe_key[3:]
                                data_emissao = req.find("{{{0}}}NFe/{{{0}}}infNFe/{{{0}}}ide/{{{0}}}dhEmi".format(
                                    NfceRequestBuilder.NAMESPACE_NFE)).text
                                serie_nota = req.find("{{{0}}}NFe/{{{0}}}infNFe/{{{0}}}ide/{{{0}}}serie".format(
                                    NfceRequestBuilder.NAMESPACE_NFE)).text
                                serie_nota = serie_nota.zfill(3)
                                numero_nota = req.find("{{{0}}}NFe/{{{0}}}infNFe/{{{0}}}ide/{{{0}}}nNF".format(
                                    NfceRequestBuilder.NAMESPACE_NFE)).text
                                numero_nota = numero_nota.zfill(9)

                                self.nfce_situation_checker.logger = logger
                                fiscal_data = self.nfce_situation_checker.check_situation_nfe(nfe)
                                if not fiscal_data:
                                    message = "Problemas ao tentar consultar a situação da nota na sefaz. Vamos tentar novamente depois."
                                    raise Exception(message)

                                logger.info("Protocolo recebido. Order %s " % order_id)
                                fiscal_data_xml = eTree.XML(fiscal_data)

                                c_stat = fiscal_data_xml.find(
                                    ("{{{0}}}Body/{{{1}}}" + tag_consulta_nf_result + "/{{{2}}}retConsSitNFe/{{{2}}}cStat").format(
                                        NfceRequestBuilder.NAMESPACE_SOAP,
                                        namespace_consulta_situacao,
                                        NfceRequestBuilder.NAMESPACE_NFE))
                                if c_stat is None:
                                    raise Exception("Campo cStat Nao Encontrado")
                                else:
                                    c_stat = c_stat.text
                                if c_stat in ("100", "150", "613"):  # 100 = ok; 613 = Chave de Acesso difere da existente em BD;
                                    if c_stat in ("613", ):
                                        try:
                                            logger.info("Regerando XML. Order %s " % order_id)
                                            order_picture = eTree.XML(base64.b64decode(order_picture + "=" * ((4 - len(order_picture) % 4) % 4)))
                                            envelopado, data_emissao, serie_nota, numero_nota = self.fiscal_processor.nfce_request_builder.build_request(order_picture, False, None, None)
                                            serie_nota = str(serie_nota).zfill(3)
                                            numero_nota = str(numero_nota).zfill(9)
                                            logger.info("XML Regerado. Order %s " % order_id)
                                            index1 = envelopado.index("<NFe")
                                            index2 = envelopado.index("</NFe>")
                                            new_req = envelopado[index1:index2 + 6]

                                            request_str = request_str[:request_str.index("<NFe ")] + new_req + request_str[request_str.index("</NFe>") + 6:]
                                            req = eTree.XML(request_str)
                                        except Exception:
                                            logger.exception("Erro ao gerar XML da order %s" % order_id)
                                            fiscalrepository.set_nfce_sent(order_id, -1)
                                            continue

                                    if c_stat in ("613", ):
                                        nfe_key = \
                                        req.find("{{{0}}}NFe/{{{0}}}infNFe".format(NfceRequestBuilder.NAMESPACE_NFE)).attrib['Id']
                                        chave_xml = nfe_key[3:]

                                        self.nfce_situation_checker.logger = logger
                                        fiscal_data = self.nfce_situation_checker.check_situation_nfe(chave_xml)
                                        if not fiscal_data:
                                            message = "Problemas ao tentar consultar a situação da nota na sefaz. Vamos tentar novamente depois."
                                            raise Exception(message)

                                        logger.info("Protocolo recebido. Order %s " % order_id)
                                        fiscal_data_xml = eTree.XML(fiscal_data)

                                        c_stat = fiscal_data_xml.find(
                                            ("{{{0}}}Body/{{{1}}}"+ tag_consulta_nf_result + "/{{{2}}}retConsSitNFe/{{{2}}}cStat").format(
                                                NfceRequestBuilder.NAMESPACE_SOAP,
                                                namespace_consulta_situacao,
                                                NfceRequestBuilder.NAMESPACE_NFE))
                                        if c_stat is None:
                                            raise Exception("Campo cStat Nao Encontrado")
                                        else:
                                            c_stat = c_stat.text
                                        if c_stat not in ("100",):
                                            raise Exception("Campo cStat diferente de 100")

                                    inf_prot = fiscal_data_xml.find(
                                        ("{{{0}}}Body/{{{1}}}" + tag_consulta_nf_result + "/{{{2}}}retConsSitNFe/{{{2}}}protNFe/{{{2}}}infProt").format(
                                            NfceRequestBuilder.NAMESPACE_SOAP,
                                            namespace_consulta_situacao,
                                            NfceRequestBuilder.NAMESPACE_NFE))
                                    inf_prot.attrib.pop("Id", None)
                                    new_req = request_str[:request_str.index("<infProt")] + eTree.tostring(inf_prot) + request_str[request_str.index("</protNFe"):]
                                    xml_base64 = base64.b64encode(new_req)
                                    fiscalrepository.set_nfce_sent_with_xml_bkoffice(order_id, xml_base64, 1, 0)

                                    dir_arquivo = os.path.join(data_emissao[0:4], data_emissao[5:7], data_emissao[8:10])
                                    dir_nota = os.path.join(self.fiscal_sent_dir, self.xml_enviados, dir_arquivo)
                                    self._create_directory_if_not_exists(dir_nota)
                                    nfce_file_name = "{0}_{1}_{2}_nfe_proc_pos{3}_{4}.xml".format(serie_nota, numero_nota, order_id.zfill(9), str(pos_id).zfill(2), nfe_key)

                                    for xml_file in glob.glob(dir_nota + '/*' + order_id.zfill(9) + '_nfe_proc_pos*'):
                                        os.remove(xml_file)

                                    with open(os.path.join(dir_nota, nfce_file_name), "w+") as nfe_proc_file:
                                        nfe_proc_file.write(new_req)
                                else:
                                    raise Exception("Status diferente de 100. %s" % c_stat)
                            except Exception:
                                logger.exception("Erro ao buscar protocolo da order %s" % order_id)
                                continue

                except Exception as _:
                    logger.exception("Erro ao Consultar Status dos XMLs")
                    return False, "Erro ao Consultar Status dos XMLs"
                finally:
                    if conn:
                        conn.close()

            return True, "Status Salvos com Sucesso"
        else:
            return False, "Loja nao opera com NFCE"

    def _handle_get_csts(self, _):
        return True, self.fiscal_parameter_controller.get_all_products_csts()

    def _handle_get_base_reductions(self, _):
        return True, self.fiscal_parameter_controller.get_all_products_base_reductions()

    def _handle_get_tip_params(self, _):
        return True, self.fiscal_parameter_controller.get_tip_params()

    def _handle_get_ibpt_tax(self, msg):
        order_xml = eTree.XML(msg.data)
        taxes_dict = self.ibpt_tax_processor.ibpt_tax_calculator(order_xml)
        return True, str(taxes_dict)

    def _handle_print_donation_coupon(self, msg):
        params = msg.data.split("\x00")
        report = self.donation_coupon_printer.print_coupon(*params)
        return True, report

    def _handle_get_contingency_status(self, _):
        return True, "Enabled" if self.nfce_contingencia.contingencia else "Disabled"

    def _handle_change_contingency_status(self, _):
        self.nfce_contingencia.force_contingency = not self.nfce_contingencia.force_contingency
        if self.nfce_contingencia.force_contingency:
            logger.info("Entrando em contingência forçada via menu suporte")
            self.nfce_contingencia.entra_contingencia("Contingencia forcada")
        else:
            self.nfce_contingencia.contingencia = False
            self.nfce_contingencia.process_now = True
            self.nfce_contingencia.start_contingencia()

        return True, "Contingency status successfully changed"

    def handle_event(self, subject, evt_type, data, msg):
        if subject == "ORDER_MODIFIED" and evt_type == "PAID":
            order = eTree.XML(data).find("Order")
            order_id = order.get("orderId")
            with FiscalDataRepository(self.mbcontext) as fiscal_repository:  # type: fiscalpersistence.FiscalDataRepository
                fiscal_repository.set_order_picture(order_id, base64.b64encode(eTree.tostring(order)))
        elif subject == "ORDER_MODIFIED" and evt_type == "VOID_ORDER":
            order = eTree.XML(data).find(".//Order")
            order_id = order.get("orderId")
            with FiscalDataRepository(self.mbcontext) as fiscal_repository:  # type: fiscalpersistence.FiscalDataRepository
                fiscal_repository.set_order_canceled(order_id)
        elif subject == "DisableNfceOrder":
            self._handle_disable_orders()
        elif subject == "ReSignXMLs":
            self._handle_re_sign_xml()
        elif subject == "OrderManagerFiscalXmlRequest":
            self._handle_fiscal_xml_request(data)
        elif subject == "CheckIfFiscalXmlIsCreatedInFolder":
            self._handle_check_if_fiscal_xml_is_created_in_folder()
        elif subject == "RetryFiscalOrdersWithException":
            self._handle_retry_fiscal_orders_with_exception()

    def _handle_search_request(self):
        if self.nf_type not in ("SAT", "MFE"):
            return False, "Loja não opera com SAT"

        sat_status_list = {}
        sat_service_finder = self.fiscal_processor.sat_service_finder  # type: SatServiceFinder

        pos_sat_list = sat_service_finder.pos_sat_list
        sat_list_to_search = pos_sat_list if pos_sat_list else range(0, sat_service_finder.max_sat_number + 1)

        for i in sat_list_to_search:
            sat_service_name = "SAT%s" % str(i).zfill(2)
            try:
                ret = self.mbcontext.MB_EasySendMessage(sat_service_name, TK_SAT_STATUS_REQUEST, format=FM_PARAM, timeout=100 * 100000)

                if ret.token == TK_SYS_ACK:
                    sat_status_list[sat_service_name] = ret.data
                else:
                    sat_status_list[sat_service_name] = "ERROR"
            except Exception as ex:
                logger.error("Erro buscando SAT - Exception: %s", str(ex))
                sat_status_list[sat_service_name] = "Desabilitado"

        return True, str(sat_status_list)

    def _handle_disable_orders(self):
        if not self.nf_type == "NFCE":
            return
        try:
            self.order_disabler.disable_all_orders(self.mbcontext)
        except Exception as _:
            logger.exception("Erro inutilizando notas")

    def _handle_sat_status_request(
        self,
    ):
        # type: (...) -> Tuple[bool, str]

        if self.nf_type not in ("SAT", "MFE"):
            message = "Loja não opera com SAT"
            logger.info(message)
            return False, message

        _, search_request = self._handle_search_request()
        search_request = eval(search_request)
        for key, value in search_request.iteritems():
            if str(value).upper() != "OK":
                continue

            logger.info("Active SAT {}".format(str(key)))
            return True, str(key)

        return False, "Sem SAT ativo na loja"

    def _handle_sat_op_status_request(
        self,
        msg,                    # type: MBMessage
    ):
        # type: (...) -> Tuple[bool, str]

        pos_id = msg.data

        if self.nf_type not in ("SAT", "MFE"):
            return False, "Loja não opera com SAT"

        sat_service_finder = self.fiscal_processor.sat_service_finder  # type: SatServiceFinder
        pos_sat_list = sat_service_finder.pos_sat_list

        pos_id_list = [pos_id] if not pos_sat_list or str(pos_id) in pos_sat_list else []

        sat_info_list = []

        for current_pos_id in pos_id_list:
            sat_service_name = "SAT{}".format(current_pos_id.zfill(2))
            try:
                response = self.mbcontext.MB_EasySendMessage(
                    dest_name=sat_service_name,
                    token=TK_SAT_OPERATIONAL_STATUS_REQUEST,
                    format=FM_PARAM,
                    timeout=100 * 100000,
                )
                sat_info_list.append(response.data)
            except MBException as ex:
                logger.info("Error searching SAT on POS#{}: {}".format(pos_id, str(ex)))
                sat_info_list.append("ERROR - Desabilitado")

        if not pos_id_list:
            sat_info_list.append("ERROR - Desabilitado")

        return True, str(sat_info_list)

    def _export_fiscal_files(self, msg):
        nok = False, 'Erro ao exportar arquivos fiscais'

        try:
            parse_ok, start_date, end_date = self._parse_date_from_fiscal_screen(msg.data)
            if not parse_ok:
                return nok

            try:
                folder_ok, folders = self._find_fiscal_folders(start_date, end_date, msg.data)
                if not folder_ok:
                    return nok
            except EmptySearchException as ex:
                return False, ex.message

            zip_ok, zip_path = self.zip_folders(folders)
            if not zip_ok:
                return nok

            return True, zip_path

        except Exception as ex:
            logger.error("Erro ao exportar arquivos fiscais %s", str(ex))

        return nok

    @staticmethod
    def _parse_date_from_fiscal_screen(dates):
        try:
            # parse das datas: yyyy/mm/dd;yyyy/mm/dd
            match = re.search(r'(\d+/\d+/\d+);(\d+/\d+/\d+)', dates)
            if match:
                dateSplit = dates.split(";")
                startDate = dateSplit[0]
                endDate = dateSplit[1]
                return True, startDate, endDate

        except Exception as ex:
            logger.error("Erro ao parsear datas (_parse_date_from_fiscal_screen) %s", str(ex))

        return False, '', ''

    def _find_fiscal_folders(self, start_date, end_date, msg):
        try:
            logger.info("start_date: " + start_date)
            logger.info("end_date: " + end_date)

            xml_type = self.xml_enviados if msg.split(";")[2] == '1' else self.xml_erros
            path = os.path.join(self.fiscal_sent_dir, xml_type)
            logger.info("path: " + path)

            export_folder = os.path.join(path, 'ExportTemp')
            logger.info("export_folder: " + export_folder)

            folders = filter(lambda f: os.path.isdir(f), glob.glob(path + '/*/*/*'))
            logger.info("lambda: " + str((lambda f: os.path.isdir(f), glob.glob(path + '/*/*/*'))))

            folders = [s.replace(path + "\\", '').replace(path + "/", '').replace('\\', '/') for s in folders]
            logger.info("folders after replace: " + str(folders))

            folders.sort()
            logger.info("folders after sort: " + str(folders))

            folders = folders[bisect.bisect_left(folders, start_date):bisect.bisect_right(folders, end_date)]
            logger.info("folders bisect_left: " + str(bisect.bisect_left(folders, start_date)))
            logger.info("folders bisect_right: " + str(bisect.bisect_right(folders, end_date)))
            logger.info("folders after bisect: " + str(folders))

            if folders:
                shutil.rmtree(export_folder, True)
                for item in folders:
                    shutil.copytree(os.path.join(path, item), os.path.join(export_folder, item))

                return True, export_folder
            else:
                raise EmptySearchException("Nenhum arquivo encontrado")

        except EmptySearchException as ex:
            raise ex

        except Exception as ex:
            logger.error("Erro ao buscar diretorios (_find_fiscal_folders) %s", str(ex))

        return False, ''

    def zip_folders(self, folder):
        try:
            zip_name = 'export'
            zip_complete_name = zip_name + '.zip'

            try:
                os.remove(os.path.join(self.export_xml_path, zip_complete_name))
            except OSError:
                pass

            self.zip(folder, zip_name)
            shutil.move(zip_complete_name, self.export_xml_path)
            shutil.rmtree(folder, True)
            return True, zip_complete_name
        except Exception as ex:
            logger.error("Erro ao zipar o diretorio temporario (zip_folders) %s", str(ex))
            shutil.rmtree(folder, True)

        return False, ''

    @staticmethod
    def zip(src, dst):
        zf = zipfile.ZipFile("%s.zip" % dst, "w", zipfile.ZIP_DEFLATED)
        abs_src = os.path.abspath(src)
        for dirname, subdirs, files in os.walk(src):
            for filename in files:
                absname = os.path.abspath(os.path.join(dirname, filename))
                arcname = absname[len(abs_src) + 1:]
                zf.write(absname, arcname)
        zf.close()

    def _handle_sat_process_payment_request(self, msg):
        if self.nf_type not in ("MFE",):
            return True, ''
        sat_service_name = self.fiscal_processor.sat_service_finder.find_and_lock_sat_service(msg.data.split("\0")[1])
        msg = self.mbcontext.MB_EasySendMessage(sat_service_name, TK_SAT_PROCESS_PAYMENT, format=FM_PARAM, data=msg.data)
        self.fiscal_processor.sat_service_finder.unlock_sat_service(sat_service_name)
        return True, msg.data

    def _handle_sat_payment_status_request(self, msg):
        if self.nf_type not in ("MFE",):
            return True, ''

        sat_service_name = self.fiscal_processor.sat_service_finder.find_and_lock_sat_service(msg.data.split("\0")[1])
        msg = self.mbcontext.MB_EasySendMessage(sat_service_name, TK_SAT_PAYMENT_STATUS, format=FM_PARAM, data=msg.data)
        self.fiscal_processor.sat_service_finder.unlock_sat_service(sat_service_name)
        return True, msg.data

    def _handle_cancel_order(
        self,   # type: FiscalWrapperEventHandler
        msg,    # type: MBMessage
    ):
        # type: (...) -> Tuple[bool, str]

        received_data = msg.data.split("|")
        pos_id = received_data[0]
        order_id = received_data[1]
        model = get_model(posid=pos_id)
        pos_ot = get_posot(model=model)

        model = get_model(pos_id)
        pos_ot = get_posot(model)

        optional_fields = {
            "fields_to_show": {
                "SaleLine": ["customParams"]
            },
        }
        order = eTree.XML(
            pos_ot.orderPicture(
                posid=pos_id,
                orderid=order_id,
                optional_fields=optional_fields,
            )
        )
        if order.tag == "Orders":
            order = order.find("Order")

        order_id = order.get('orderId')
        pos_id = order.get('posId')

        justification = self._get_cancel_order_justification(
            model=model,
            received_data=received_data,
        )
        log_message = "Processando cancelamento do order: {}. Justificativa: {}"
        logger_canceler.info(log_message.format(order_id, justification))

        fiscalization_date = order.find(".//CustomOrderProperties/OrderProperty[@key='FISCALIZATION_DATE']")
        if fiscalization_date is None:
            return False, "Pedido ainda não fiscalizado"

        formatted_fiscalization_date = datetime.strptime(fiscalization_date.get("value"), "%Y-%m-%dT%H:%M:%S")
        since_datetime = datetime.now() - timedelta(minutes=30)
        if convert_from_utf_to_localtime(formatted_fiscalization_date).replace(tzinfo=None) < since_datetime:
            return False, "Pedido excedeu o tempo limite para cancelamento fiscal (30 minutos)"

        response = (False, msg.data)
        try:
            if self.nf_type == 'NFCE':
                response = self._cancel_nfce(
                    pos_id=pos_id,
                    order_id=order_id,
                    justification=justification,
                )

            elif self.nf_type in ['SAT', 'MFE']:
                response = self._cancel_sat(
                    pos_id=pos_id,
                    order_id=order_id,
                )

            return response
        finally:
            if response and len(response) == 2 and response[0] is True:
                logger_canceler.info("Cancelamento realizado com sucesso. Status: {}".format(response[1]))

    @staticmethod
    def _get_cancel_order_justification(
        model,          # type: eTree.Element
        received_data,  # type: List[str]
    ):
        # type: (...) -> str

        justification = received_data[2] if len(received_data) > 2 else None
        justification = justification if justification is not None else "USER_FISCAL_CANCELLATION"
        justification = remove_accents(
            text=translate_message(
                model=model,
                message_key=justification,
            ),
        )
        justification = "CANCELAMENTO: {}".format(justification)

        return justification

    def _cancel_nfce(
        self,
        pos_id,         # type: str
        order_id,       # type: str
        justification,  # type: str
    ):
        # type: (...) -> Tuple[bool, str]

        with FiscalDataRepository(mbcontext=self.mbcontext) as fiscal_repository:
            order_data = fiscal_repository.get_xml_response(order_id=order_id)
            if order_data is None:
                return False, "Só é possível cancelar vendas já autorizadas"

            response = self.nfce_canceler.cancel_order(
                order_response=order_data,
                justification=justification,
                order_id=order_id,
            )

            if response.get("status"):
                self.order_service.set_custom_property(
                    pos_id=pos_id,
                    order_id=order_id,
                    key="CANCELED_AFTER_PAID",
                    value="true",
                )

                if response.get("xml_response") is not None:
                    self.save_cancel_xml(
                        pos_id=pos_id,
                        order_id=order_id,
                        data=response.get("xml_response"),
                    )

            return response.get("status"), response.get("message")

    def _cancel_sat(
        self,
        pos_id,     # type: str
        order_id,   # type: str
    ):
        # type: (...) -> Tuple[bool, str]

        with FiscalDataRepository(self.mbcontext) as fiscal_repository:
            order_data = fiscal_repository.get_numero_sat_and_xml_request(order_id=order_id)
            response = self.fiscal_processor.cancel_sale(
                order_data=order_data,
                order_id=order_id,
            )
            if response.get("status") and response.get("xml_response", None) is not None:
                self.save_cancel_xml(pos_id, order_id, response.get("xml_response"))

            if self.nf_type == 'MFE' and response.get("status") and self.fiscal_processor.cancel_data:
                try:
                    self.printers[""].print_cancel(pos_id, self.fiscal_processor.cancel_data)
                except Exception as _:
                    logger.exception("Error print cancel fiscal data")

            return response.get("status"), response.get("message")

    def save_cancel_xml(
            self,
            pos_id,     # type: str
            order_id,   # type: str
            data        # type: str
    ):
        # type: (...) -> None

        with FiscalDataRepository(self.mbcontext) as fiscal_repository:
            fiscal_repository.set_fiscal_xml_custom_property(
                pos_id=pos_id,
                xml_request=data,
                order_id=order_id,
                key='CANCELED_FISCAL_XML'
            )

    @staticmethod
    def _busca_parametros_sitef(order):
        # type: (eTree) -> (str, str, str)
        order_id = order.get("orderId")
        created_at = order.get("createdAt").replace("-", "").replace(":", "")
        data_fiscal = created_at[:8]
        hora_fiscal = created_at[9:15]

        return order_id, data_fiscal, hora_fiscal

    def _reprint_sat_or_nfce(
        self,           # type: FiscalWrapperEventHandler
        xml_request,    # type: str
        xml_response,   # type: str
        params,         # type: eTree
        danfe_type,     # type: str
    ):
        # type: (...) -> Tuple[bool, unicode]

        if not xml_request:
            return False, "XML do pedido não encontrado"

        if self.nf_type in ("SAT", "MFE"):
            return self._reprint_sat(
                xml_request=xml_request,
                danfe_type=danfe_type,
                params=params,
            )

        xml_request_decoded = base64.b64decode(xml_request)
        start_index = xml_request_decoded.find("<tpEmis>") + 8
        end_index = xml_request_decoded.find("</tpEmis>")
        emission_type = xml_request_decoded[start_index:end_index]
        is_contingency = emission_type == "9" and xml_response is None

        return self._reprint_nfce(
            xml_request=xml_request,
            xml_response=xml_response,
            params=params,
            danfe_type=danfe_type,
            is_contingency=is_contingency,
        )

    def _reprint_sat(
        self,           # type: FiscalWrapperEventHandler
        xml_request,    # type: str
        params,         # type: eTree
        danfe_type,     # type: str
    ):
        # type: (...) -> Tuple[bool, unicode]

        ret_xml_request = self._get_xml_sat(xml_request)
        if None in [ret_xml_request['fiscal_data'], ret_xml_request['xml_request']]:
            return False, "Erro ao obter XML da venda!"

        selected_printer = self._get_printer(danfe_type=danfe_type)

        return (
            True,
            selected_printer.print_fiscal(
                pos_id=params.get('posid'),
                order=params.get('order'),
                tenders=params.get('tenders'),
                fiscal_request=ret_xml_request.get("xml_request", ""),
                fiscal_response=eTree.XML(ret_xml_request.get("fiscal_data", "")),
                total_taxes=params.get("total_taxes"),
                is_reprint=True,
            ),
        )

    def _reprint_nfce(
        self,               # type: FiscalWrapperEventHandler
        xml_request,        # type: str
        xml_response,       # type: str
        params,             # type: eTree
        danfe_type,         # type: str
        is_contingency,     # type: bool
    ):
        # type: (...) -> Tuple[bool, unicode]

        if is_contingency:
            xml_request_decoded = base64.b64decode(xml_request)
            ret_xml_request = {"request_xml":  eTree.XML(xml_request_decoded), "response_xml": None}
        else:
            try:
                ret_xml_request = self._get_xml_nfce(xml_request, xml_response)
            except StopIteration as ex:
                return False, ex.message

            if None in [ret_xml_request['request_xml'], ret_xml_request['response_xml']]:
                return False, "Erro ao obter XML da venda!"

        selected_printer = self._get_printer(danfe_type=danfe_type)

        return (
            True,
            selected_printer.print_fiscal(
                pos_id=params.get('posid'),
                order=params.get('order'),
                tenders=params.get('tenders'),
                fiscal_request=ret_xml_request.get('request_xml'),
                fiscal_response=ret_xml_request.get('response_xml'),
                total_taxes=params.get("total_taxes"),
                url_sefaz=params.get('url_sefaz'),
                is_reprint=True,
            ),
        )

    def _get_printer(
        self,           # type: FiscalWrapperEventHandler
        danfe_type,     # type: str
    ):
        # type: (...) -> printer.FiscalPrinter

        return self.printers[danfe_type] if danfe_type in self.printers else self.printers[""]

    @staticmethod
    def _get_xml_sat(xml_request):
        try:
            xml = eTree.XML(base64.b64decode(xml_request))
            ide = xml.find('infCFe').find('ide')
            sat_key = xml.find('infCFe').attrib.get('Id')

            cpf_cnpj = xml.find('infCFe').find('dest') or ""
            if cpf_cnpj:
                if cpf_cnpj.find("CPF") is not None:
                    cpf_cnpj = cpf_cnpj.find("CPF").text
                elif cpf_cnpj.find("CNPJ") is not None:
                    cpf_cnpj = cpf_cnpj.find("CNPJ").text
                else:
                    cpf_cnpj = None

            date_time = "{0}{1}".format(ide.find('dEmi').text, ide.find('hEmi').text)
            date_time_reprint = datetime.strptime(date_time, "%Y%m%d%H%M%S").strftime("%d/%m/%y %H:%M:%S")

            total = xml.find('infCFe').find('total')

            customer_doc = cpf_cnpj if cpf_cnpj is not None else 'Nao informado'

            qr_code_values = [sat_key[3:],
                              date_time,
                              total.find('vCFe').text,
                              customer_doc,
                              ide.find('assinaturaQRCODE').text]

            qr_code = "|".join(qr_code_values)

            list_tags_xml = ['vICMS', 'vPIS', 'vCOFINS', 'vPISST', 'vCOFINSST']
            total_taxes = 0
            icms_total = total.find('ICMSTot')
            for x in list_tags_xml:
                try:
                    total_taxes += D(icms_total.find(x).text)
                except (TypeError, AttributeError):
                    pass
            total_taxes = round_half_away_from_zero(total_taxes, 2)

            tag_xml_request = eTree.XML("<XmlRequest>{0}</XmlRequest>".format(xml_request))
            tag_fiscal_data = "<FiscalData>"
            tag_fiscal_data += "<datetime>{0}</datetime>".format(date_time_reprint)
            tag_fiscal_data += "<satno>{0}</satno>".format(ide.find('nserieSAT').text)
            tag_fiscal_data += "<satkey>{0}</satkey>".format(sat_key[3:])
            tag_fiscal_data += "<CustomerCPF>{0}</CustomerCPF>".format(customer_doc)
            tag_fiscal_data += "<QRCode>{0}</QRCode>".format(qr_code)
            tag_fiscal_data += "</FiscalData>"

            return {"xml_request": tag_xml_request, "fiscal_data": tag_fiscal_data, "total_taxes": total_taxes}
        except Exception as ex:
            logger.error("Erro ao construir o xml - Exception %s", str(ex))
            return {"xml_request": None, "fiscal_data": None, "total_taxes": 0}

    def _get_xml_nfce(self, xml_request, xml_response):
        try:
            xml = remove_xml_namespace(base64.b64decode(xml_request))
            xml_resp = remove_xml_namespace(base64.b64decode(xml_response))

            response_tag = "retConsReciNFe"
            if self.synchronous_mode:
                response_tag = "retEnviNFe"
            xml_ws_version = int(float(xml_resp.find(".//{}".format(response_tag)).get('versao')))

            if xml_ws_version != self.versao_ws:
                error_message = "Versão do XML diferente da versão fiscal atual. Versão XML: {}; Versão Fiscal: {}"
                raise StopIteration(error_message.format(xml_ws_version, self.versao_ws))

            c_stat_xml_response = self._get_c_stat_code(xml_data=xml_resp)
            c_stat_xml_request = self._get_c_stat_code(xml_data=xml)
            if c_stat_xml_response == NfceCstatCode.duplicity and c_stat_xml_request == NfceCstatCode.authorized:
                xml_resp = xml

            nprot = xml_resp.find(".//infProt/nProt").text
            data_resp = xml_resp.find(".//infProt/dhRecbto").text

            if xml.find('.//NFe'):
                body = eTree.tostring(xml.find('.//NFe'))
            else:
                body = eTree.tostring(xml)

            version = xml.find(".//infNFe").attrib['versao']
            total = xml.find('.//ICMSTot')

            list_tags_xml = ['vICMS', 'vPIS', 'vCOFINS', 'vPISST', 'vCOFINSST']
            total_taxes = 0

            for tag in list_tags_xml:
                try:
                    total_taxes += D(total.find('.//{}'.format(tag)).text)
                except (TypeError, AttributeError):
                    pass

            total_taxes = round_half_away_from_zero(total_taxes, 2)

            tag_request = '<ns0:Envelope xmlns = "http://www.portalfiscal.inf.br/nfe" ' \
                          'xmlns:ns0 = "http://www.w3.org/2003/05/soap-envelope" ' \
                          'xmlns:ns1 = "http://www.portalfiscal.inf.br/nfe/wsdl/NfeAutorizacao" ' \
                          'xmlns:ns3 = "http://www.w3.org/2000/09/xmldsig#">'
            tag_request += '<ns0:Body><ns1:nfeDadosMsg>'
            tag_request += '<enviNFe versao = "{0}">'.format(version)
            tag_request += '{0}'.format(body)
            tag_request += '</enviNFe></ns1:nfeDadosMsg></ns0:Body></ns0:Envelope>'

            tag_reponse = '<ns0:Envelope xmlns="http://www.portalfiscal.inf.br/nfe" ' \
                          'xmlns:ns0="http://www.w3.org/2003/05/soap-envelope" ' \
                          'xmlns:ns1="http://www.portalfiscal.inf.br/nfe/wsdl/NfeRetAutorizacao">' \
                          '<ns0:Body><ns1:nfeRetAutorizacaoLoteResult>'
            tag_reponse += '<retConsReciNFe versao="{0}">'.format(version)
            tag_reponse += '<protNFe versao="{0}"><infProt>'.format(version)
            tag_reponse += '<dhRecbto>{0}</dhRecbto>'.format(str(data_resp))
            tag_reponse += '<nProt>{0}</nProt>'.format(str(nprot))

            re_search = r"<[A-z0-9]*?:?{0}>(.*?)</([A-z0-9]*?):?{0}>"
            if regex_search(re_search, eTree.tostring(xml_resp), key="cMsg"):
                message_code = regex_search(re_search, eTree.tostring(xml_resp), key="cMsg")
                message = regex_search(re_search, eTree.tostring(xml_resp), key="xMsg")
                if message_code == '200' and message not in [None, '', ' ']:
                    tag_reponse += "<cMsg>{}</cMsg>".format(message_code)
                    tag_reponse += "<xMsg>{}</xMsg>".format(message)

            tag_reponse += '</infProt></protNFe></retConsReciNFe></ns1:nfeRetAutorizacaoLoteResult></ns0:Body></ns0:Envelope>'

            return {"request_xml": eTree.XML(tag_request), "response_xml": eTree.XML(tag_reponse), "total_taxes": total_taxes}
        except StopIteration:
            raise
        except Exception as ex:
            logger.error("Erro ao construir o xml - Exception %s", str(ex))
            return {"request_xml": None, "response_xml": None, "total_taxes": 0}

    def _handle_re_sign_xml(self):
        with FiscalDataRepository(self.mbcontext) as fiscalrepository:
            logger.info("Starting XML Resign Event...")
            conn = None
            try:
                conn = DBDriver().open(self.mbcontext, service_name="FiscalPersistence")
                sql = """SELECT PosId, OrderId, OrderPicture, XMLRequest
                          FROM fiscal.FiscalData
                          WHERE SentToNfce IN (555)
                          ORDER BY OrderId ASC LIMIT 500"""

                cursor = conn.select(sql)
                orders_with_error = 0
                orders_with_success = 0
                for row in cursor:
                    try:
                        pos_id, order_id, order_picture, request = map(row.get_entry, ("PosId", "OrderId", "OrderPicture", "XMLRequest"))
                        logger.info("Resigning Order %s" % order_id)

                        if self.nf_type != "PAF":
                            try:
                                is_in_contingence = True
                                xml_decoded = base64.b64decode(request + "=" * ((4 - len(request) % 4) % 4))
                                req = remove_xml_namespace(xml_decoded)

                                dh_cont = req.find(".//infNFe/ide/dhCont")
                                if dh_cont is None:
                                    dh_cont = req.find(".//NFe/infNFe/ide/dhCont")

                                x_just = req.find(".//infNFe/ide/xJust")
                                if x_just is None:
                                    x_just = req.find(".//NFe/infNFe/ide/xJust")

                                if dh_cont is not None:
                                    dh_cont = dh_cont.text
                                    dh_cont = dh_cont[:-6]
                                    dh_cont = datetime.strptime(dh_cont, "%Y-%m-%dT%H:%M:%S")
                                if x_just is not None:
                                    x_just = x_just.text
                                if dh_cont is None and x_just is None:
                                    dh_cont = datetime.now()
                                    x_just = "Problemas de conexao com a SEFAZ"
                            except Exception as _:
                                dh_cont = datetime.now()
                                x_just = "Problemas de conexao com a SEFAZ"

                                logger.info("Order: {} - dh_cont: {} / xjust: {}".format(order_id, dh_cont, x_just))
                        else:
                            is_in_contingence = False
                            dh_cont = ""
                            x_just = ""
                            logger.info("Order: {} - Not in contingence".format(order_id))

                        order_taker = OrderTaker()
                        order = order_taker.get_order_picture(pos_id, order_id)
                        fiscalrepository.set_order_picture(order_id, base64.b64encode(order))
                        order = eTree.XML(order)

                        order.attrib["posId"] = pos_id

                        fiscal_data = self.fiscal_processor.nfce_request_builder.build_request(order, is_in_contingence, dh_cont, x_just)
                        index1 = fiscal_data[0].index("<NFe")
                        index2 = fiscal_data[0].index("</NFe>")
                        new_req = fiscal_data[0][index1:index2 + 6]
                        status = 0
                        xml_base64 = base64.b64encode(new_req)

                        fiscalrepository.set_nfce_sent_with_xml(order_id, xml_base64, status)
                        logger.info("XML Resign Order %s OK" % order_id)
                        orders_with_success += 1

                    except Exception as _:
                        orders_with_error += 1
                        logger.exception("Error to build XML of the order %s" % order_id)
                        continue
                if orders_with_error:
                    logger.error("Orders with success: {0}. Orders with error: {1}".format(orders_with_success, orders_with_error))
                    return False, "Orders with success: {0}. Orders with error: {1}".format(orders_with_success, orders_with_error)

            except Exception as _:
                logger.exception("Error to resign XMLs")
                return False, "Error to resign XMLs"
            finally:
                logger.info("Finish XML Resign Event...")
                if conn:
                    conn.close()

        return True, "Success to resigns XMLs. Number of orders resigns: {}".format(orders_with_success)

    def _handle_fiscal_xml_request(self, data, return_data=False):
        conn = None
        base64_xml_response = None
        order_id = None
        try:
            conn = DBDriver().open(self.mbcontext, service_name="FiscalPersistence")
            sql = """SELECT OrderId, XMLRequest
                      FROM fiscal.FiscalData
                      WHERE OrderId=%s""" % data
            cursor = conn.select(sql)
            for row in cursor:
                order_id = row.get_entry(0)
                base64_xml_response = row.get_entry(1)
        except Exception:
            logger.exception("Erro ao buscar XMLRequest")
        finally:
            if conn:
                conn.close()
        if return_data:
            if order_id is None:
                return False, "Invalid Order ID {}".format(data)
            if base64_xml_response is None:
                return False, "No XML for order ID {}".format(order_id)
            return True, base64_xml_response
        else:
            if order_id is None:
                self.mbcontext.MB_EasyEvtSend("PosFiscalXmlError", "", ";".join((order_id, "OrderId inválida ou inexistente no banco - order %s" % data)))
            elif base64_xml_response is None:
                self.mbcontext.MB_EasyEvtSend("PosFiscalXmlError", "", ";".join((order_id, "XML inexistente para order %s" % order_id)))
            else:
                xml_response = base64.b64decode(base64_xml_response)
                compress_obj = zlib.compressobj(zlib.Z_BEST_COMPRESSION, zlib.DEFLATED, 31)
                zipped_xml = compress_obj.compress(xml_response)
                zipped_xml += compress_obj.flush()
                base64_zipped_xml = base64.b64encode(zipped_xml)
                self.mbcontext.MB_EasyEvtSend("PosFiscalXmlResponse", "", ";".join((order_id, base64_zipped_xml)))

    def _handle_check_if_fiscal_xml_is_created_in_folder(self):
        # type: () -> None
        try:
            logger.info("Starting -> _handle_check_if_fiscal_xml_is_created_in_folder")
            date_yesterday, date_yesterday_start, date_yesterday_end = self._get_yesterday_dates()
            fiscal_data_path = self._get_fiscal_xml_sent_path(date_yesterday)

            database_fiscal_data_number_list = self._get_fiscal_data_number_from_database_by_date(date_yesterday_start, date_yesterday_end)

            self._generate_fiscal_xml_not_created(database_fiscal_data_number_list, fiscal_data_path)
            logger.info("Finished -> _handle_check_if_fiscal_xml_is_created_in_folder")
        except Exception as _:
            logger.exception("Error creating XML files")

    def _handle_retry_fiscal_orders_with_exception(self):
        # type: () -> None
        try:
            logger.info("Starting -> _handle_retry_fiscal_orders_with_exception")
            with FiscalDataRepository(self.mbcontext) as fiscal_repository:  # type: fiscalpersistence.FiscalDataRepository
                fiscal_repository.retry_fiscal_orders_with_exception(self.period_to_retry_orders_with_exception)

            logger.info("Finished -> _handle_retry_fiscal_orders_with_exception")
        except Exception as _:
            logger.exception("Error changing status of fiscal orders with exception")

    def _get_fiscal_xml_sent_path(self, date_yesterday):
        fiscal_data_file = str(date_yesterday.year) + '/' + str(date_yesterday.month).zfill(2) + '/' + str(date_yesterday.day).zfill(2)
        fiscal_data_path = os.path.join(self.fiscal_sent_dir, self.xml_enviados, fiscal_data_file)
        return fiscal_data_path

    def _generate_fiscal_xml_not_created(self, database_fiscal_data_number_list, fiscal_data_path):
        self._create_directory_if_not_exists(fiscal_data_path)

        self._verify_if_xml_in_folder_is_in_database_list(database_fiscal_data_number_list, fiscal_data_path)
        self._create_xml_if_it_is_not_in_folder(database_fiscal_data_number_list, fiscal_data_path)

    def _create_xml_if_it_is_not_in_folder(self, database_fiscal_data_number_list, fiscal_data_path):
        conn = None
        try:
            conn = DBDriver().open(self.mbcontext, service_name="FiscalPersistence")
            sql = """SELECT PosId, OrderId, XMLRequest, NumeroNota, NumeroSat FROM FiscalData
                     WHERE NumeroNota IN ({}) """.format("'" + "','".join(database_fiscal_data_number_list) + "'")

            cursor = conn.select(sql)
            for row in cursor:
                numero_nota, order_id, pos_id, xml_request, numero_sat = self._parse_database_info(row)

                inf_id, numero_serie = self._parse_xml_info(xml_request)

                fiscal_xml_path_to_save = self.create_fiscal_xml_path_to_save(inf_id, numero_serie,
                                                                              fiscal_data_path, numero_nota, order_id,
                                                                              pos_id, numero_sat)
                self._save_xml_file_in_folder(fiscal_xml_path_to_save, xml_request)
        except Exception as ex:
            logger.exception("Exception creating XML with error: {}".format(ex))
        finally:
            conn.close()

    def create_fiscal_xml_path_to_save(self, inf_id, numero_serie, fiscal_data_path, numero_nota, order_id, pos_id, numero_sat):
        if self.nf_type in ["NFCE", "PAF"]:
            fiscal_xml_path_to_save = FiscalWrapperEventHandler._generate_nfce_xml_file_name(inf_id,
                                                                                             numero_nota,
                                                                                             order_id,
                                                                                             pos_id,
                                                                                             numero_serie,
                                                                                             fiscal_data_path)
        else:

            fiscal_xml_path_to_save = FiscalWrapperEventHandler._generate_sat_xml_file_name(fiscal_data_path,
                                                                                            numero_nota,
                                                                                            order_id,
                                                                                            pos_id,
                                                                                            numero_sat,
                                                                                            inf_id)
        return fiscal_xml_path_to_save

    @staticmethod
    def _generate_nfce_xml_file_name(infnfe_id, numero_nota, order_id, pos_id, numero_serie, fiscal_data_path):
        fiscal_xml_path_to_save = os.path.join(fiscal_data_path, "{0}_{1}_{2}_nfe_proc_pos{3}_{4}.xml".format(
            numero_serie.zfill(3),
            numero_nota.zfill(9),
            order_id.zfill(9),
            pos_id.zfill(2),
            infnfe_id))
        return fiscal_xml_path_to_save

    @staticmethod
    def _generate_sat_xml_file_name(fiscal_data_path, numero_nota, order_id, pos_id, numero_sat, infcfe_id):
        fiscal_xml_path_to_save = os.path.join(fiscal_data_path, "S{0}_{1}_{2}_response_pos{3}_{4}.xml".format(
            numero_sat[-2:],
            str(numero_nota).zfill(9),
            str(order_id).zfill(9),
            str(pos_id).zfill(2),
            infcfe_id))
        return fiscal_xml_path_to_save

    @staticmethod
    def _save_xml_file_in_folder(fiscal_xml_path_to_save, fiscal_xml_to_save):
        with open(fiscal_xml_path_to_save, "w+") as xml_to_save:
            xml_to_save.write(fiscal_xml_to_save)

    def _parse_xml_info(self, fiscal_xml_to_save):
        fiscal_xml_to_save = remove_xml_namespace(fiscal_xml_to_save)

        key_fiscal_inf = fiscal_xml_to_save.find(".//infNFe") if self.nf_type in ("NFCE", "PAF") else fiscal_xml_to_save.find(".//infCFe")
        data_fiscal_inf = key_fiscal_inf.attrib['Id']

        key_data_fiscal_serie = fiscal_xml_to_save.find(".//serie") if self.nf_type in ("NFCE", "PAF") else fiscal_xml_to_save.find(".//nserieSAT")
        data_fiscal_serie = key_data_fiscal_serie.text.zfill(3)

        return data_fiscal_inf, data_fiscal_serie

    @staticmethod
    def _parse_database_info(row):
        numero_nota = str(row.get_entry("NumeroNota")).zfill(9)
        order_id = str(row.get_entry("OrderId")).zfill(9)
        pos_id = str(row.get_entry("PosId")).zfill(2)
        xml_request = base64.b64decode(row.get_entry("XMLRequest"))
        numero_sat = str(row.get_entry("NumeroSat"))
        return numero_nota, order_id, pos_id, xml_request, numero_sat

    def _verify_if_xml_in_folder_is_in_database_list(self, database_fiscal_data_number_list, fiscal_data_path):
        for xml_file in glob.glob(os.path.join(fiscal_data_path, '*.xml')):
            try:
                xml_fiscal_number = self._get_xml_fiscal_number_from_xml_file(xml_file)

                if xml_fiscal_number in database_fiscal_data_number_list:
                    database_fiscal_data_number_list.remove(xml_fiscal_number)
                elif not 'procInut' in xml_file:
                    self._move_xml_to_inconsistent_folder(fiscal_data_path, xml_file)

            except Exception as _:
                logger.exception("Exception on File: " + xml_file + "\nMoved to folder: " + self.xml_inconsistentes)
                self._move_xml_to_inconsistent_folder(fiscal_data_path, xml_file)

    def _get_xml_fiscal_number_from_xml_file(self, xml_file):
        xml = remove_xml_namespace(eTree.tostring(eTree.parse(xml_file).getroot()))
        key = xml.find(".//nNF") if self.nf_type == "NFCE" else xml.find(".//nCFe")
        return key.text if key is not None else None

    def _move_xml_to_inconsistent_folder(self, fiscal_data_path, xml_file):
        inconsistent_fiscal_data_path = fiscal_data_path.replace(self.xml_enviados, self.xml_inconsistentes)
        self._create_directory_if_not_exists(inconsistent_fiscal_data_path)
        xml_file_new_path = xml_file.replace(self.xml_enviados, self.xml_inconsistentes)
        shutil.move(xml_file, xml_file_new_path)
        logger.info("File: " + xml_file + "\nMoved to folder: " + self.xml_inconsistentes)

    @staticmethod
    def _create_directory_if_not_exists(data_path):
        try:
            if not os.path.exists(data_path):
                os.makedirs(data_path)
        except OSError:
            logger.error("Cannot create path: {}".format(data_path))

    @staticmethod
    def _get_yesterday_dates():
        date_yesterday = datetime.now() - timedelta(days=1)
        date_yesterday_start = datetime(date_yesterday.year, date_yesterday.month, date_yesterday.day, 0, 0, 0)
        date_yesterday_end = datetime(date_yesterday.year, date_yesterday.month, date_yesterday.day, 23, 59, 59)
        return date_yesterday, date_yesterday_start, date_yesterday_end

    def _get_fiscal_data_number_from_database_by_date(self, start_date, end_date):
        # type: (datetime, datetime) -> [unicode]
        fiscal_data_number_list = []
        conn = None

        try:
            conn = DBDriver().open(self.mbcontext, service_name="FiscalPersistence")
            sql = """SELECT NumeroNota, datetime(datanota,'unixepoch','localtime') AS data 
                     FROM fiscal.FiscalData
                     WHERE data > '{}' AND data <= '{}'""".format(start_date, end_date)

            cursor = conn.select(sql)
            for row in cursor:
                fiscal_data_number_list.append(row.get_entry("NumeroNota"))
        except Exception as ex:
            logger.exception("Get fiscal data number list error: " + ex.message)
        finally:
            conn.close()

        return fiscal_data_number_list

    @staticmethod
    def _get_c_stat_code(
        xml_data,
    ):
        # type: (...) -> Optional[str]

        c_stat = xml_data.find(".//infProt/cStat")

        if c_stat is not None:
            return c_stat.text

        return None
