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

import logging
import os
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 (
    Tuple,
    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 import NfeBuilder
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 (
    FeeService,
    SequencerService,
)
from sat import (
    SatProcessor,
    SatRequestBuilder,
    SatServiceFinder,
)
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")
    config_logger(
        loader_path=loader_cfg,
        log_name=service_name + "XmlLog",
    )

    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=config,
        group_path="FiscalWrapper.FiscalParameters",
    )
    fees_fiscal_parameters = _get_config_group(
        config=config,
        group_path="FiscalWrapper.FeesFiscalParameters",
    )
    loader_fiscal_parameters.extend(fees_fiscal_parameters)

    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=config,
        fiscal_parameters=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
    )
    fee_service = FeeService(
        loader_config=config,
        logger=logger,
        calculate_taxes_common=calculate_taxes_common,
        fiscal_parameter_controller=fiscal_parameter_controller,
    )
    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")

        sat_service_finder = SatServiceFinder(
            mbcontext=mbcontext,
            max_sat_number=max_sat_number,
            pos_sat_list=pos_sat_list,
            sleep_time=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,
            fee_service=fee_service,
        )

        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,
        )
        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"):
        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, dev_ambiente = _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
            ambiente = dev_ambiente

        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=serie,
            mod=mod,
            ambiente=ambiente,
            fiscal_parameter_controller=fiscal_parameter_controller,
            sale_line_util=sale_line_util,
            cid_token=cid_token,
            csc=csc,
            qrcode_base_url=nfce_urls["qrcode_base_url"],
            nfe_signer=nfe_signer,
            versao_ws=versao_ws,
            qrcode_check_url=nfce_urls["qrcode_check_url"],
            software_developer=software_developer,
            ibtp_tax_processor=ibtp_tax_processor,
            customer_text_field=customer_text_field,
            tenders_type=tenders_type,
            tip_builder_common=tip_builder_common,
            store_model=store_model,
            fee_service=fee_service,
        )

        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):
    default_nfce_urls = "../data/server/bundles/fiscalwrapper/nfce_urls.xml"
    urls_path = sys_parsefilepath(nfce_group.find_value("urls_path")) or default_nfce_urls
    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,                          # type: int
    mod,                            # type: int
    ambiente,                       # type: str
    fiscal_parameter_controller,    # type: FiscalParameterController
    sale_line_util,                 # type: SaleLineUtil
    cid_token,                      # type: str
    csc,                            # type: str
    qrcode_base_url,                # type: str
    nfe_signer,                     # type: NfeSigner
    versao_ws,                      # type: int
    qrcode_check_url,               # type: str
    software_developer,             # type: SoftwareDeveloper
    ibtp_tax_processor,             # type: IbptTaxProcessor
    customer_text_field,            # type: str
    tenders_type,                   # type: Dict[str, Any]
    tip_builder_common,             # type: TipBuilderCommon
    store_model,                    # type: Store
    fee_service,                    # type: FeeService
):
    # type: (...) -> NfeBuilder

    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_fee_builder = nfcebuilder.DetFeeBuilder(
        det_builder=det_builder,
        fee_service=fee_service,
    )
    det_list_builder = nfcebuilder.DetListBuilder(
        det_builder,
        det_tip_builder,
        det_fee_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
    fee_service,                    # type: FeeService
):
    # 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_fee_builder = satbuilder.DetFeeBuilder(
        det_builder=det_builder,
        fee_service=fee_service,
    )
    det_list_builder = satbuilder.DetListBuilder(
        det_builder,
        det_tip_builder,
        det_fee_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,):
        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,):
        return {}


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

    try:
        groups = _get_config_group(
            config=config,
            group_path="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,):
        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(";")
                ambiente = "2"
                return int(serie_inc), int(initial_order_id_inc), ambiente

    return None, 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"

        print_loyalty_delivery_order = config.find_value(
            "FiscalWrapper.Loyalty.PrintLoyaltyCouponForDeliveryOrder"
        ) or "False"

        text_to_print = config.find_value(
            "FiscalWrapper.Loyalty.TextToPrintInLoyaltyCoupon"
        ) or ""

        return LoyaltyConfigurations(
            print_loyalty_coupon_after_fiscalization=print_loyalty.lower() == "true",
            print_loyalty_coupon_delivery_orders=print_loyalty_delivery_order.lower() == "true",
            text_to_print_in_loyalty_coupon=text_to_print,
            store_name=read_swconfig(
                mbcontext=mb_context,
                key="Store.Name",
            ) or "",
        )
    except (Exception,):
        return LoyaltyConfigurations(
            print_loyalty_coupon_after_fiscalization=False,
            print_loyalty_coupon_delivery_orders=False,
            text_to_print_in_loyalty_coupon="",
            store_name="",
        )


if __name__ == "__main__":
    main()
