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

import logging
import os
import re
import xml.etree.ElementTree as eTree

import cfgtools
import pyscripts
import sysactions
from cfgtools import Configuration
from helper import import_pydevd, remove_accents
from messagehandler import MessageHandler
from modelcommon.model import Store
from msgbus import MBEasyContext
from old_helper import OrderTaker, PosUtil, config_logger, read_swconfig, remove_punctuation
from pos_model import OrderParser
from pos_util import SaleLineUtil
from systools import sys_parsefilepath
from typing import (
    Union,
    Dict,
    List,
    Any,
)

import nfcebuilder
import satbuilder
from aposprinter import SatAposA8FiscalPrinter, NfceAposA8FiscalPrinter
from clichehelper import ClicheHelper
from common import FiscalParameterController, TipBuilderCommon, PrinterCommon, CalculateTaxesItemCommon
from eventhandler import FiscalWrapperEventHandler
from ibpttaxprocessor import IbptTaxProcessor
from models import (
    SoftwareDeveloper,
    LoyaltyConfigurations,
)
from nfce import NfceProcessor, NfceRequestBuilder, NfceContingencia, NfceAutorizador, NfceConnectivityTester, \
    NfceSituationChecker
from nfcebuilder.nfceutil import NfeSigner, XmlEnveloper, NfceRequest
from nfcecanceler import NfceCanceler, NfceCancelerRequestBuilder, NfceCancelerParameters, NfceCancelerResponseParser
from nfcedisabler import OrderDisabler, NfceDisabler, NfceDisablerRequestBuilder, NfceDisablerParameters, \
    NfceDisablerResponseParser, OrderRepository, ProcInutNfeBuilder, ProcInutNfeSaver
from order_retriever import OrderRetrieverService, OrderDataFormatter
from order_retriever.repository import OrderRetrieverRepository
from printer import SatFiscalPrinter, NfceFiscalPrinter, DonationCouponPrinter
from repository import FiscalParameterRepository, SequencerRepository, ProductRepository
from satbuilder import CfeBuilder
from services import SequencerService
from sat import SatProcessor, SatRequestBuilder, SatServiceFinder, MfeServiceFinder
from operator import itemgetter

REQUIRED_SERVICES = "FiscalPersistence|PosController|StoreWideConfig"


def main():
    loader_cfg = os.environ["LOADERCFG"]
    import_pydevd(loader_cfg, 9125)

    config = cfgtools.read(loader_cfg)
    service_name = str(config.find_value("Identification.ComponentName"))
    logger = logging.getLogger(service_name)

    config_logger(loader_cfg, service_name)
    config_logger(loader_cfg, service_name + "Thread")
    config_logger(loader_cfg, service_name + "NfceDisabler")
    config_logger(loader_cfg, service_name + "NfceCanceler")
    config_logger(loader_cfg, service_name + "Coupons")

    xml_enviados = str(config.find_value("FiscalWrapper.XmlEnviados"))
    xml_erros = str(config.find_value("FiscalWrapper.XmlErros"))
    xml_inconsistentes = str(config.find_value("FiscalWrapper.XmlInconsistentes") or "Inconsistentes")
    export_xml_path = os.path.join(sys_parsefilepath(str(config.find_value("FiscalWrapper.XmlPath"))), service_name)
    crt = int(config.find_value("FiscalWrapper.crt"))
    ibpt_tax_files_path = sys_parsefilepath(str(config.find_value("FiscalWrapper.IbptTaxFilesPath") or "../data/bundles/fiscalwrapper/ibpttaxfiles"))

    mbcontext = MBEasyContext(service_name)
    pyscripts.mbcontext = mbcontext
    sysactions.mbcontext = mbcontext

    nf_type = config.find_value("FiscalWrapper.NFType")
    order_ref_ids = _get_order_ref_ids(config)
    order_ref_position = (config.find_value("FiscalWrapper.OrderReferencePosition") or "BETWEEN_FISCAL_DATA").upper()

    fiscal_printer = None
    mobile_fiscal_printer = None
    fiscal_processor = None

    fiscal_sent_dir = sys_parsefilepath(config.find_value("FiscalWrapper.FiscalSentDir").strip())

    message_handler = MessageHandler(mbcontext, service_name, service_name, REQUIRED_SERVICES)

    nome_emit = remove_accents(read_swconfig(mbcontext, "Store.RazaoSocial"))
    end_logradouro = _limit_max_length(remove_accents(read_swconfig(mbcontext, "Store.EndLogradouro")), 60)
    end_numero = remove_punctuation(read_swconfig(mbcontext, "Store.EndNumero").strip()) or "S/N"
    end_compl = remove_accents((read_swconfig(mbcontext, "Store.EndCompl") or "").strip())
    bairro = remove_accents(read_swconfig(mbcontext, "Store.Bairro"))
    municipio = remove_accents(read_swconfig(mbcontext, "Store.Municipio"))
    cep = remove_punctuation(read_swconfig(mbcontext, "Store.CEP"))
    uf = remove_accents(read_swconfig(mbcontext, "Store.UF"))
    cnpj_contribuinte = remove_punctuation(config.find_value("FiscalWrapper.CnpjContribuinte"))
    inscr_estadual = remove_punctuation(config.find_value("FiscalWrapper.InscEstadual"))

    store_model = Store(
        crt,
        nome_emit,
        cnpj_contribuinte,
        inscr_estadual,
        cep,
        end_logradouro,
        end_numero,
        end_compl,
        bairro,
        municipio,
        uf
    )

    cliche_helper = ClicheHelper(mbcontext, cnpj_contribuinte, inscr_estadual)

    order_disabler = None
    nfce_contingencia = None
    nfce_situation_checker = None
    nfce_connectivity_tester = None
    nfce_canceler = None
    versao_ws = None
    synchronous_mode = False

    loader_fiscal_parameters = _get_config_group(config, "FiscalWrapper.FiscalParameters")
    fiscal_coupon_configurations = _get_fiscal_coupon_configurations(config=config)
    fiscal_parameters = FiscalParameterRepository(mbcontext).get_fiscal_parameters(loader_fiscal_parameters)
    tenders_type = ProductRepository(mbcontext).get_tender_types()

    customer_text_field = config.find_value("FiscalWrapper.PrinterCustomizations.CustomerTextField") or ""
    print_order_identifier = (config.find_value("FiscalWrapper.PrinterCustomizations.PrintOrderIdentifier") or "false").lower() == "true"
    customer_call_by_field = config.find_value("FiscalWrapper.PrinterCustomizations.YouWillBeCalledBy") or ""

    period_to_retry_orders_with_exception = config.find_value("FiscalWrapper.PeriodToRetryOrdersWithException") or "7"

    data_path = sys_parsefilepath("{HVDATADIR}")
    database_dir = os.path.join(data_path, "databases")

    default_fiscal_path = "../../src/persistcomp/fiscal/fiscal.ddl"
    fiscal_ddl_path = config.find_value("FiscalWrapper.FiscalSchemaDatabasePath", default=default_fiscal_path)
    fiscal_schema_sql_path = os.path.join(data_path, fiscal_ddl_path)

    order_parser = OrderParser()

    tip_config = _get_tip_config(config)
    tip_config_range = _get_tip_config_by_range(config, fiscal_parameters)
    calculate_taxes_common = CalculateTaxesItemCommon(mbcontext)
    fiscal_max_value = float(config.find_value("FiscalWrapper.MaxValueToFiscalize") or "10000")
    fiscal_parameter_controller = FiscalParameterController(
        fiscal_parameters=fiscal_parameters,
        tip_config_range=tip_config_range,
        loader_fiscal_parameters=loader_fiscal_parameters,
        fiscal_max_value=fiscal_max_value,
    )
    tip_builder_common = TipBuilderCommon(
                    tip_config,
                    tip_config_range,
                    mbcontext,
                    fiscal_parameter_controller,
                    calculate_taxes_common
    )
    printer_common = PrinterCommon(
        tip_config=tip_config,
        tip_config_range=tip_config_range,
        tip_builder_common=tip_builder_common,
        customer_call_by_field=customer_call_by_field,
    )
    loyalty_configurations = _get_loyalty_configurations(
        mb_context=mbcontext,
        config=config,
    )

    ibtp_tax_processor = IbptTaxProcessor(mbcontext, ibpt_tax_files_path, uf, fiscal_parameter_controller)

    sale_line_util = SaleLineUtil()

    order_retriever_repository = OrderRetrieverRepository(mbcontext)
    order_data_formatter = OrderDataFormatter()
    order_retriever_service = OrderRetrieverService(order_retriever_repository, order_data_formatter)

    initial_order_id = None
    font_size_withdrawal_panel = _get_font_size_withdrawal_panel_mobile(
        config=config,
    )

    danfe_font_size_mobile = _get_danfe_font_size_mobile(
      config=config,
    )

    if nf_type.upper() == "SAT" or nf_type.upper() == "MFE":
        sat_group = config.find_group("FiscalWrapper.SAT")
        cnpj_sw = sat_group.find_value("CnpjSW")
        sign_ac = sat_group.find_value("SignAC")
        max_sat_number = int(sat_group.find_value("MaxSatNumber"))
        pos_sat_list = sat_group.find_value("PosSatList")
        sleep_time = int(sat_group.find_value("SearchSatInterval"))
        layout_version = sat_group.find_value("LayoutVersion", default="0.07")
        is_mfe = nf_type.upper() == "MFE"

        if nf_type.upper() == "SAT":
            sat_service_finder = SatServiceFinder(mbcontext, max_sat_number, pos_sat_list, sleep_time)
        else:
            sat_service_finder = MfeServiceFinder(mbcontext, max_sat_number, pos_sat_list, sleep_time)

        cfe_builder = _build_sat_builders(
            crt=crt,
            cnpj_contribuinte=cnpj_contribuinte,
            inscr_estadual=inscr_estadual,
            cnpj_sw=cnpj_sw,
            sign_ac=sign_ac,
            fiscal_parameter_controller=fiscal_parameter_controller,
            sale_line_util=sale_line_util,
            tip_config=tip_config,
            ibtp_tax_processor=ibtp_tax_processor,
            customer_text_field=customer_text_field,
            tenders_type=tenders_type,
            tip_builder_common=tip_builder_common,
            layout_version=layout_version,
        )

        sat_request_builder = SatRequestBuilder(
            mbcontext=mbcontext,
            crt=crt,
            cnpj_sw=cnpj_sw,
            sign_ac=sign_ac,
            cnpj_contribuinte=cnpj_contribuinte,
            inscr_estadual=inscr_estadual,
            order_parser=order_parser,
            fiscal_parameter_controller=fiscal_parameter_controller,
            sale_line_util=sale_line_util,
            cfe_builder=cfe_builder,
            is_mfe=is_mfe,
        )
        fiscal_processor = SatProcessor(mbcontext, sat_service_finder, sat_request_builder, fiscal_sent_dir)
        fiscal_printer = SatFiscalPrinter(
            mb_context=mbcontext,
            order_ref_ids=order_ref_ids,
            cliche_helper=cliche_helper,
            customer_text_field=customer_text_field,
            print_order_identifier=print_order_identifier,
            printer_common=printer_common,
            coupon_fiscal_parameters=fiscal_coupon_configurations,
            order_ref_position=order_ref_position,
            loyalty_configurations=loyalty_configurations,
        )
        mobile_fiscal_printer = SatAposA8FiscalPrinter(
            mb_context=mbcontext,
            order_ref_ids=order_ref_ids,
            cliche_helper=cliche_helper,
            customer_text_field=customer_text_field,
            printer_common=printer_common,
            coupon_fiscal_parameters=fiscal_coupon_configurations,
            font_size_withdrawal_panel=font_size_withdrawal_panel,
            danfe_font_size_mobile=danfe_font_size_mobile,
            order_ref_position=order_ref_position,
            loyalty_configurations=loyalty_configurations,
        )

    elif nf_type.upper() in ("NFCE", "PAF"):
        nfce_group = config.find_group("FiscalWrapper.NFCE")

        c_uf = nfce_group.find_value("cUF")
        c_mun_fg = nfce_group.find_value("cMunFG")
        mod = nfce_group.find_value("mod")
        serie = nfce_group.find_value("serie")
        ambiente = nfce_group.find_value("ambiente")
        csc = nfce_group.find_value("csc")
        cid_token = nfce_group.find_value("cid_token")
        initial_order_id = int(nfce_group.find_value("initial_order_id"))

        serie_inc, initial_order_id_inc = _get_development_serie_and_order_id_increment()
        if serie_inc is not None:
            serie = str(int(serie) + serie_inc)
            initial_order_id += initial_order_id_inc

        versao_ws = int(nfce_group.find_value("versao_ws"))
        send_sleep_time = int(nfce_group.find_value("send_sleep_time") or 5)
        check_situation_timeout = int(nfce_group.find_value("CheckSituationTimeout") or 10)
        synchronous_mode = (nfce_group.find_value("synchronous_mode") or "false").lower() == "true"

        store_model.city_code = c_mun_fg
        store_model.state_code = c_uf

        software_developer = _read_software_developer_informations(config, uf)

        bundle_dir = os.environ["BUNDLEDIR"]
        certificate_key_path = bundle_dir + nfce_group.find_value("certificate_key_path")
        certificate_path = bundle_dir + nfce_group.find_value("certificate_path")
        nfe_signer = NfeSigner(certificate_key_path, certificate_path)
        sefaz_certificate_path = bundle_dir + nfce_group.find_value("sefaz_certificate_path")

        nfce_urls = _get_nfce_urls(logger, nfce_group, uf, versao_ws, ambiente)

        nfe_builder = _build_nfe_builders(
                serie,
                mod,
                ambiente,
                fiscal_parameter_controller,
                sale_line_util,
                cid_token,
                csc,
                nfce_urls["qrcode_base_url"],
                nfe_signer,
                versao_ws,
                nfce_urls["qrcode_check_url"],
                software_developer,
                ibtp_tax_processor,
                customer_text_field,
                tenders_type,
                tip_builder_common,
                store_model
        )

        nfce_request_builder = NfceRequestBuilder(mbcontext, initial_order_id, crt, cnpj_contribuinte, inscr_estadual,
                                                  c_uf, uf, c_mun_fg, mod, serie, ambiente, end_logradouro, end_numero,
                                                  end_compl, bairro, municipio, cep, nome_emit, csc, cid_token,
                                                  nfce_urls["qrcode_base_url"], certificate_key_path, certificate_path, order_parser,
                                                  fiscal_parameter_controller, sale_line_util, nfe_builder, versao_ws, synchronous_mode)
        nfce_request = NfceRequest(certificate_key_path, certificate_path, sefaz_certificate_path, check_situation_timeout)

        max_tantavivas_envio_lote = int(nfce_group.find_value("max_tantavivas_envio_lote") or "3")
        max_tantavivas_envio_contingencia = int(nfce_group.find_value("max_tantavivas_envio_contingencia") or "10")
        intervalo_retentativa_lote = int(nfce_group.find_value("intervalo_retentativa_lote") or "15")



        if nf_type.upper() == "NFCE":
            nfce_connectivity_tester = NfceConnectivityTester(nfce_request, nfce_urls["url_status_servico"], c_uf, versao_ws)
            nfce_situation_checker = NfceSituationChecker(nfce_request, nfce_urls["url_consulta_nfe"], ambiente, c_uf, versao_ws)
            nfce_autorizador = NfceAutorizador(nfce_request_builder, nfce_request, nfce_urls["url_autorizacao"], nfce_urls["url_ret_autorizacao"], versao_ws, max_tantavivas_envio_lote, intervalo_retentativa_lote, send_sleep_time, synchronous_mode)
            nfce_contingencia = NfceContingencia(
                mbcontext=mbcontext,
                nfce_request_builder=nfce_request_builder,
                nfce_autorizador=nfce_autorizador,
                versao_ws=versao_ws,
                nfce_situation_checker=nfce_situation_checker,
                fiscal_sent_dir=fiscal_sent_dir,
                max_tantavivas_envio_contingencia=max_tantavivas_envio_contingencia,
                synchronous_mode=synchronous_mode,
                nfce_connectivity_tester=nfce_connectivity_tester,
                database_dir=database_dir,
                fiscal_schema_sql_path=fiscal_schema_sql_path,
            )
            fiscal_printer = NfceFiscalPrinter(
                mb_context=mbcontext,
                order_ref_ids=order_ref_ids,
                cliche_helper=cliche_helper,
                qrcode_check_url=nfce_urls["qrcode_check_url"],
                versao_ws=versao_ws,
                customer_text_field=customer_text_field,
                print_order_identifier=print_order_identifier,
                printer_common=printer_common,
                coupon_fiscal_parameters=fiscal_coupon_configurations,
                order_ref_position=order_ref_position,
                loyalty_configurations=loyalty_configurations,
            )
            mobile_fiscal_printer = NfceAposA8FiscalPrinter(
                mb_context=mbcontext,
                order_ref_ids=order_ref_ids,
                cliche_helper=cliche_helper,
                qrcode_url=nfce_urls["qrcode_check_url"],
                customer_text_field=customer_text_field,
                printer_common=printer_common,
                coupon_fiscal_parameters=fiscal_coupon_configurations,
                font_size_withdrawal_panel=font_size_withdrawal_panel,
                danfe_font_size_mobile=danfe_font_size_mobile,
                order_ref_position=order_ref_position,
                loyalty_configurations=loyalty_configurations,
            )
        else:
            nfce_request = None
            nfce_autorizador = None
            nfce_contingencia = None
            fiscal_printer = None
            nfce_situation_checker = None

        fiscal_processor = NfceProcessor(
            mbcontext=mbcontext,
            nfce_request_builder=nfce_request_builder,
            nfce_request=nfce_request,
            nfce_autorizador=nfce_autorizador,
            nfce_contingencia=nfce_contingencia,
            url_sefaz=nfce_urls["qrcode_base_url"],
            fiscal_sent_dir=fiscal_sent_dir,
            versao_ws=versao_ws,
            synchronous_mode=synchronous_mode,
            nfce_situation_checker=nfce_situation_checker,
            xml_enviados=xml_enviados,
        )

        justificativa_inutilizacao = nfce_group.find_value("justificativa_inutilizacao")
        nfce_disabler_parameters = NfceDisablerParameters(initial_order_id, ambiente, c_uf, cnpj_contribuinte, serie, justificativa_inutilizacao)
        nfce_canceler_parameters = NfceCancelerParameters(ambiente, c_uf, cnpj_contribuinte)

        xml_enveloper = XmlEnveloper(c_uf)
        nfce_disabler_request_builder = NfceDisablerRequestBuilder(nfce_disabler_parameters, nfe_signer, xml_enveloper, versao_ws)
        nfce_canceler_request_builder = NfceCancelerRequestBuilder(nfce_canceler_parameters, nfe_signer, xml_enveloper)

        nfce_disabler_response_parser = NfceDisablerResponseParser(versao_ws)
        nfce_canceler_response_parser = NfceCancelerResponseParser()

        nfce_canceler = NfceCanceler(nfce_canceler_request_builder, nfce_canceler_response_parser, nfce_request, nfce_urls["url_cancelamento"], fiscal_sent_dir, versao_ws)

        proc_inut_nfe_builder = ProcInutNfeBuilder()
        proc_inut_nfe_saver = ProcInutNfeSaver(fiscal_sent_dir)
        nfce_disabler = NfceDisabler(nfce_disabler_request_builder, nfce_disabler_response_parser, nfce_request, nfce_urls["url_inutilizacao"], proc_inut_nfe_builder, proc_inut_nfe_saver, versao_ws)

        order_taker = OrderTaker()

        pos_list = PosUtil(mbcontext).get_pos_list()
        order_repository = OrderRepository(mbcontext, pos_list)

        order_disabler = OrderDisabler(order_repository, nfce_disabler, order_taker)

    sequencer_repository = SequencerRepository(mbcontext)
    sequencer_service = SequencerService(sequencer_repository)

    last_order_id = int(order_retriever_service.get_last_order_id())
    initial_order_id = initial_order_id or 0
    sequencer_service.update_fiscal_id_sequence(initial_order_id, last_order_id)

    printers = {
        "": fiscal_printer,
        "APOS_A8": mobile_fiscal_printer,
        "EXTERNAL": mobile_fiscal_printer,
    }
    donation_coupon_printer = DonationCouponPrinter(cliche_helper, nf_type.upper())

    event_handler = FiscalWrapperEventHandler(
        mbcontext=mbcontext,
        fiscal_processor=fiscal_processor,
        printers=printers,
        nfce_connectivity_tester=nfce_connectivity_tester,
        order_disabler=order_disabler,
        nfce_canceler=nfce_canceler,
        fiscal_parameter_controller=fiscal_parameter_controller,
        xml_enviados=xml_enviados,
        xml_erros=xml_erros,
        export_xml_path=export_xml_path,
        fiscal_sent_dir=fiscal_sent_dir,
        nf_type=nf_type,
        versao_ws=versao_ws,
        ibpt_tax_processor=ibtp_tax_processor,
        order_retriever_service=order_retriever_service,
        xml_inconsistentes=xml_inconsistentes,
        donation_coupon_printer=donation_coupon_printer,
        nfce_contingencia=nfce_contingencia,
        period_to_retry_orders_with_exception=period_to_retry_orders_with_exception,
        synchronous_mode=synchronous_mode,
        sequencer_service=sequencer_service,
    )
    message_handler.set_event_handler(event_handler)

    if nfce_contingencia is not None:
        nfce_contingencia.start_contingencia()

    message_handler.subscribe_events([
        "ORDER_MODIFIED",
        "DisableNfceOrder",
        "ReSignXMLs",
        "OrderManagerFiscalXmlRequest",
        "CheckIfFiscalXmlIsCreatedInFolder",
        "RetryFiscalOrdersWithException"
    ])

    message_handler.handle_events()


def _get_nfce_urls(logger, nfce_group, uf, versao_ws, ambiente):
    urls_path = sys_parsefilepath(nfce_group.find_value("urls_path")) or "../data/server/bundles/fiscalwrapper/nfce_urls.xml"
    nfce_urls = {}

    if os.path.exists(urls_path):
        logger.info("Reading NFCe URLs from <nfce_urls.xml>...")
        urls_xml = eTree.parse(urls_path).getroot()
        for state in urls_xml.findall("estado"):
            if state.get("uf") == uf:
                for version in state.findall("versao"):
                    if int(version.get("ws")) == versao_ws and version.get("ambiente") == ambiente:
                        for url in version:
                            nfce_urls[url.tag] = str.strip(url.text)

    if not nfce_urls:
        logger.info("NFCe URLs not found in <nfce_urls.xml>, reading from <loader.cfg>...")
        nfce_urls["url_autorizacao"] = nfce_group.find_value("url_autorizacao") or ""
        nfce_urls["url_ret_autorizacao"] = nfce_group.find_value("url_ret_autorizacao") or ""
        nfce_urls["url_inutilizacao"] = nfce_group.find_value("url_inutilizacao") or ""
        nfce_urls["url_cancelamento"] = nfce_group.find_value("url_cancelamento") or ""
        nfce_urls["url_status_servico"] = nfce_group.find_value("url_status_servico") or ""
        nfce_urls["url_consulta_nfe"] = nfce_group.find_value("url_consulta_nfe") or ""
        nfce_urls["qrcode_base_url"] = nfce_group.find_value("qrcode_base_url") or ""
        nfce_urls["qrcode_check_url"] = nfce_group.find_value("qrcode_check_url") or ""

    for url in nfce_urls:
        if not nfce_urls[url]:
            logger.warning("NFCe URL missing: [{}]".format(url))

    return nfce_urls


def _get_config_group(config, group_path):
    group = config.find_group(group_path)
    configs = []
    if group:
        for group in group.groups:
            config = {group.descr: group.name}
            for key in group.keys:
                config[key.name] = key.values[0]
            configs.append(config)
    return configs


def _build_nfe_builders(
        serie,
        mod,
        ambiente,
        fiscal_parameter_controller,
        sale_line_util,
        cid_token,
        csc,
        qrcode_base_url,
        nfe_signer,
        versao_ws,
        qrcode_check_url,
        software_developer,
        ibtp_tax_processor,
        customer_text_field,
        tenders_type,
        tip_builder_common,
        store_model
):

    ide_builder = nfcebuilder.IdeBuilder(serie, mod, ambiente, versao_ws, tenders_type, store_model)
    emit_builder = nfcebuilder.EmitBuilder(store_model)

    crt = store_model.crt
    dest_builder = nfcebuilder.DestinatarioBuilder(ambiente)
    inf_resp_tec_builder = nfcebuilder.InfRespTecBuilder(software_developer)
    det_builder = nfcebuilder.DetBuilder(ambiente, crt, fiscal_parameter_controller, sale_line_util, versao_ws)
    det_tip_builder = nfcebuilder.DetTipBuilder(det_builder, tip_builder_common)
    det_list_builder = nfcebuilder.DetListBuilder(det_builder, det_tip_builder)
    total_builder = nfcebuilder.TotalBuilder(versao_ws)
    transp_builder = nfcebuilder.TranspBuilder(store_model)
    inf_intermed_builder = nfcebuilder.InfIntermedBuilder()
    pag_builder = nfcebuilder.PagBuilder(versao_ws, tenders_type)
    adic_builder = nfcebuilder.InfAdicBuilder(customer_text_field, ibtp_tax_processor)

    builders = [ide_builder,
                emit_builder,
                dest_builder,
                det_list_builder,
                total_builder,
                transp_builder,
                pag_builder,
                inf_intermed_builder,
                adic_builder,
                inf_resp_tec_builder]

    c_uf = store_model.state_code
    cnpj_contribuinte = store_model.cnpj

    inf_nfe_builder = [nfcebuilder.InfNfeBuilder(builders, c_uf, cnpj_contribuinte, mod, serie, versao_ws)]
    nfe_supl_builder = nfcebuilder.InfNFeSuplBuilder(ambiente, cid_token, csc, qrcode_base_url, qrcode_check_url, versao_ws)
    nfe_builder = nfcebuilder.NfeBuilder(inf_nfe_builder, nfe_signer, nfe_supl_builder)
    return nfe_builder


def _build_sat_builders(
    crt,                            # type: int
    cnpj_contribuinte,              # type: str
    inscr_estadual,                 # type: str
    cnpj_sw,                        # type: str
    sign_ac,                        # type: str
    fiscal_parameter_controller,    # type: FiscalParameterController
    sale_line_util,                 # type: SaleLineUtil
    tip_config,                     # type: Dict[str, Any]
    ibtp_tax_processor,             # type: IbptTaxProcessor
    customer_text_field,            # type: str
    tenders_type,                   # type: Dict[str, Any]
    tip_builder_common,             # type: TipBuilderCommon
    layout_version,                 # type: str
):
    # type: (...) -> CfeBuilder

    ide_builder = satbuilder.IdeBuilder(cnpj_sw, sign_ac)
    emit_builder = satbuilder.EmitBuilder(cnpj_contribuinte, inscr_estadual)
    dest_builder = satbuilder.DestinatarioBuilder()
    det_builder = satbuilder.DetBuilder(crt, fiscal_parameter_controller, sale_line_util, ibtp_tax_processor)
    det_tip_builder = satbuilder.DetTipBuilder(det_builder, tip_config, tip_builder_common)
    det_list_builder = satbuilder.DetListBuilder(det_builder, det_tip_builder)
    total_builder = satbuilder.TotalBuilder(ibtp_tax_processor)
    pgto_builder = satbuilder.PgtoBuilder(tenders_type)
    adic_builder = satbuilder.InfAdicBuilder(customer_text_field, ibtp_tax_processor)
    builders = [ide_builder, emit_builder, dest_builder, det_list_builder, total_builder, pgto_builder, adic_builder]
    inf_cfe_builder = [
        satbuilder.InfCfeBuilder(
            part_builders=builders,
            layout_version=layout_version,
        )
    ]
    cfe_builder = satbuilder.CfeBuilder(inf_cfe_builder)
    return cfe_builder


def _read_software_developer_informations(config, uf):
    # type: (Configuration, unicode) -> Union[SoftwareDeveloper, None]
    sv_group = config.find_group("FiscalWrapper.SoftwareDeveloper")
    software_developer = SoftwareDeveloper()

    if sv_group:
        software_developer.cnpj = str(sv_group.find_value("CNPJ") or "")
        software_developer.contact = str(sv_group.find_value("Contact") or "")
        software_developer.email = str(sv_group.find_value("Email") or "")
        software_developer.phone = str(sv_group.find_value("Phone") or "")
        software_developer.csrt = str(sv_group.find_value("CSRT") or "")
        software_developer.id_csrt = str(sv_group.find_value("idCSRT") or "")

        enabled_states = sv_group.find_values("States")
        software_developer.enabled = True if uf in enabled_states else False

    return software_developer


def _limit_max_length(value, max_length):
    return value.strip()[:max_length]


def _get_tip_config(config):
    try:
        tip_name = config.find_value("FiscalWrapper.TipConfigs.TipName") or None
        tip_code = config.find_value("FiscalWrapper.TipConfigs.TipCode")
        if tip_name is None:
            return {}
        return {'TipName': tip_name, 'TipCode': tip_code}
    except Exception as _:
        return None


def _get_fiscal_coupon_configurations(
    config,     # type: eTree.XML
):
    # type: (...) -> Dict[str, bool]

    try:
        return dict(
            operator=(config.find_value("FiscalWrapper.CouponAttrs.OPERATOR") or "True").lower() == "true",
            order_or_table=(config.find_value("FiscalWrapper.CouponAttrs.ORDER_OR_TABLE") or "True").lower() == "true",
            pos_id=(config.find_value("FiscalWrapper.CouponAttrs.POS_ID") or "True").lower() == "true",
        )
    except Exception as _:
        return {}


def _get_tip_config_by_range(config, fiscal_parameters):
    # type: (Dict, Dict) -> Union[dict, List[dict]]

    try:
        groups = _get_config_group(config, "FiscalWrapper.TipConfigs.TipCode")
        if len(groups) == 0:
            return {}

        configurations = {}
        for group in groups:
            configurations[str(group.get("id"))] = _get_configuration_tip(group, fiscal_parameters)

        return sorted(configurations.values(), key=itemgetter('PercentageUpper'))
    except Exception as _:
        return {}


def _get_configuration_tip(group, fiscal_parameters):
    # type: (Dict, Dict) -> Dict

    tip_name = str(group.get("TipName"))
    max_percentage = float(group.get("PercentageUpper") or 99999)
    tip_id = str(group.get("id"))
    key = (tip_id, "ICMS")
    exempts_code = (40, 41, 50, 60)
    exempt = True if key not in fiscal_parameters or fiscal_parameters[key] in exempts_code else False
    return {'TipName': tip_name, 'TipCode': tip_id, 'PercentageUpper': max_percentage, "exempt": exempt}


def _get_font_size_withdrawal_panel_mobile(
    config,     # type: Any
):
    # type: (...) -> str

    font_size = (config.find_value("FiscalWrapper.PrinterCustomizations.FontSizeWithdrawalPanelMobile") or "large")
    font_size = font_size.lower()

    if font_size in ['small', 'large']:
        return font_size

    return "large"


def _get_danfe_font_size_mobile(
  config,   # type: Any
):
    # type: (...) -> str

    font_size = (config.find_value("FiscalWrapper.PrinterCustomizations.DanfePOSMobileFontSize") or "small")
    return font_size.lower()


def _get_order_ref_ids(config):
    default_ref_ids = "PAGER_NUMBER|CUSTOMER_NAME|ORDER_ID"
    return config.find_value("FiscalWrapper.OrderReferenceIdentifier", default_ref_ids).upper().split("|")


def _get_development_serie_and_order_id_increment():
    if os.environ.get("DEBUG", "").lower() == "true":
        initial_order_id_inc_file_path = os.path.join(os.environ["BUNDLEDIR"], "initial_order_id.txt")
        if os.path.exists(initial_order_id_inc_file_path):
            with open(initial_order_id_inc_file_path, "rb") as f:
                file_content = f.read().decode("utf-8").strip()
                serie_inc, initial_order_id_inc = file_content.split(";")
                return int(serie_inc), int(initial_order_id_inc)

    return None, None


def _get_loyalty_configurations(
    mb_context,     # type: MBEasyContext
    config,         # type: eTree.XML
):
    # type: (...) -> LoyaltyConfigurations

    try:
        print_loyalty = config.find_value("FiscalWrapper.Loyalty.PrintLoyaltyCouponAfterFiscalization") or "False"
        text_to_print = config.find_value("FiscalWrapper.Loyalty.TextToPrintInLoyaltyCoupon") or ""
        return LoyaltyConfigurations(
            print_loyalty_coupon_after_fiscalization=print_loyalty.lower() == "true",
            text_to_print_in_loyalty_coupon=text_to_print,
            store_name=read_swconfig(
                mbcontext=mb_context,
                key="Store.Name",
            ) or "",
        )
    except Exception as _:
        return LoyaltyConfigurations(
            print_loyalty_coupon_after_fiscalization=False,
            text_to_print_in_loyalty_coupon="",
            store_name="",
        )


if __name__ == "__main__":
    main()
