<?php

namespace App\Services;

use NFePHP\NFe\Tools;
use NFePHP\Common\Certificate;

class NFeService
{
    /**
     * Armazena dados de IBS/CBS para adicionar ao XML após a geração (DEPRECATED - não usar mais)
     */
    private $dadosIBSCBS = [];

    /**
     * Armazena dados de IBSCBSTot para adicionar ao XML no total
     */
    private $dadosIBSCBSTot = [];

    /**
     * Log personalizado no projeto
     */
    private function log($message)
    {
        // Usar storage/logs do projeto principal
        $basePath = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
        $logFile = $basePath . "/storage/logs/nfe_debug.log";
        $logDir = dirname($logFile);

        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }

        $timestamp = date('Y-m-d H:i:s');
        $logMessage = "[$timestamp] $message" . PHP_EOL;
        file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);

        // Também log no PHP para compatibilidade
        error_log($message);
    }

    /**
     * Log específico para comunicação com SEFAZ (timeout, erros de conexão, etc)
     */
    private function logSefaz($message, $context = [])
    {
        // Usar storage/logs do projeto principal
        $basePath = dirname(dirname(dirname(dirname(dirname(__DIR__)))));
        $logFile = $basePath . "/storage/logs/nfe_sefaz.log";
        $logDir = dirname($logFile);

        if (!is_dir($logDir)) {
            mkdir($logDir, 0755, true);
        }

        $timestamp = date('Y-m-d H:i:s');
        $contextStr = !empty($context) ? " | Contexto: " . json_encode($context, JSON_UNESCAPED_UNICODE) : "";
        $logMessage = "[$timestamp] $message$contextStr" . PHP_EOL;
        file_put_contents($logFile, $logMessage, FILE_APPEND | LOCK_EX);

        // Também log no PHP para compatibilidade
        error_log("SEFAZ: $message");
    }

    /**
     * Emite NFe sem banco de dados - 100% REAL e OFICIAL
     */
    public function emitirSemBanco($dados)
    {
        try {
            $this->log("🚀 EMISSÃO 100% REAL E OFICIAL");

            // CORREÇÃO: Normalizar estrutura dos dados para aceitar JSON com objeto 'nfe'
            if (isset($dados['nfe'])) {
                $this->log("🔧 Normalizando estrutura do JSON com objeto 'nfe'");
                $dados['numero'] = $dados['nfe']['numero'] ?? $dados['numero'] ?? '1';
                $dados['serie'] = $dados['nfe']['serie'] ?? $dados['serie'] ?? '001';
                $dados['data_emissao'] = $dados['nfe']['data_emissao'] ?? $dados['data_emissao'] ?? date('Y-m-d H:i:s');
                $this->log("✅ Número normalizado: " . $dados['numero']);
                $this->log("✅ Série normalizada: " . $dados['serie']);

                // FORÇAR USO DOS DADOS ORIGINAIS
                $dados['numero_original'] = $dados['nfe']['numero'];
                $dados['serie_original'] = $dados['nfe']['serie'];
            }

            // Criar estrutura de diretórios
            $this->criarEstruturaDiretorios($dados['empresa']['cnpj']);

            // Gerar XML da NFe usando NFePHP oficial
            $xml = $this->gerarXMLReal($dados);

            // Assinar XML
            $xmlAssinado = $this->assinarXMLReal($xml, $dados['empresa']);

            // Enviar para SEFAZ REAL
            $resultado = $this->enviarParaSEFAZReal($xmlAssinado, $dados);

            if ($resultado['success']) {
                // Usar XML protocolado se disponível, caso contrário usar XML assinado
                $xmlParaPDF = $resultado['xml_protocolado'] ?? $xmlAssinado;

                // Salvar XML protocolado se disponível
                if ($resultado['xml_protocolado']) {
                    $this->log("✅ Salvando XML protocolado");
                    $xmlPath = $this->salvarXMLReal($resultado['xml_protocolado'], $dados);
                } else {
                    $this->log("⚠️ XML protocolado não disponível, salvando XML assinado");
                    $xmlPath = $this->salvarXMLReal($xmlAssinado, $dados);
                }

                // Gerar PDF com o XML protocolado, protocolo e CHAVE REAL da SEFAZ
                $pdfPath = $this->gerarPDFReal($dados, $xmlParaPDF, $resultado['protocolo'] ?? null, $resultado['chave_acesso']);

                return [
                    'success' => true,
                    'numero' => $dados['numero_original'] ?? $dados['numero'],
                    'serie' => $dados['serie_original'] ?? $dados['serie'],
                    'chave_acesso' => $resultado['chave_acesso'],
                    'protocolo' => $resultado['protocolo'],
                    'cStat' => $resultado['cStat'] ?? null,
                    'xml_path' => $xmlPath,
                    'pdf_path' => $pdfPath,
                    'data_emissao' => $dados['data_emissao'],
                    'valor_total' => $dados['itens'][0]['valor_total'],
                    'message' => 'NFe emitida com sucesso'
                ];
            } else {
                return [
                    'success' => false,
                    'chave_acesso' => $resultado['chave_acesso'] ?? null,
                    'cStat' => $resultado['cStat'] ?? null,
                    'numero' => $dados['numero_original'] ?? $dados['numero'],
                    'serie' => $dados['serie_original'] ?? $dados['serie'],
                    'error' => $resultado['error']
                ];
            }
        } catch (\Exception $e) {
            $this->log("❌ Exceção capturada em emitirSemBanco: " . $e->getMessage());
            error_log("NFeService::emitirSemBanco - Exceção: " . $e->getMessage());
            error_log("NFeService::emitirSemBanco - Arquivo: " . $e->getFile() . ":" . $e->getLine());
            error_log("NFeService::emitirSemBanco - Stack trace: " . $e->getTraceAsString());

            // Extrair chave de acesso
            $chaveAcesso = null;
            try {
                if (isset($xmlAssinado)) {
                    $chaveAcesso = $this->extrairChaveAcessoDoXML($xmlAssinado);
                }
            } catch (\Exception $ex) {
                // Ignorar
            }

            // Tentar extrair cStat da mensagem de erro
            $cStat = null;
            if (preg_match('/\[(\d+)\]:/', $e->getMessage(), $matches)) {
                $cStat = $matches[1];
            }

            return [
                'success' => false,
                'chave_acesso' => $chaveAcesso,
                'cStat' => $cStat,
                'numero' => $dados['numero_original'] ?? $dados['numero'],
                'serie' => $dados['serie_original'] ?? $dados['serie'],
                'error' => $e->getMessage()
            ];
        } catch (\Throwable $e) {
            $this->log("❌ Erro fatal em emitirSemBanco: " . $e->getMessage());
            error_log("NFeService::emitirSemBanco - Erro fatal: " . $e->getMessage());
            error_log("NFeService::emitirSemBanco - Arquivo: " . $e->getFile() . ":" . $e->getLine());
            error_log("NFeService::emitirSemBanco - Stack trace: " . $e->getTraceAsString());

            return [
                'success' => false,
                'chave_acesso' => null,
                'cStat' => null,
                'numero' => $dados['numero_original'] ?? $dados['numero'] ?? '1',
                'serie' => $dados['serie_original'] ?? $dados['serie'] ?? '001',
                'error' => 'Erro fatal: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Gera XML REAL usando NFePHP oficial
     */
    public function gerarXMLReal($dados)
    {
        try {
            $this->log("🚀 GERANDO XML REAL COM NFePHP OFICIAL");

            // CORREÇÃO: Normalizar estrutura dos dados para aceitar JSON com objeto 'nfe'
            if (isset($dados['nfe'])) {
                $this->log("🔧 Normalizando estrutura do JSON com objeto 'nfe'");
                $dados['numero'] = $dados['nfe']['numero'] ?? $dados['numero'] ?? '1';
                $dados['serie'] = $dados['nfe']['serie'] ?? $dados['serie'] ?? '001';
                $dados['data_emissao'] = $dados['nfe']['data_emissao'] ?? $dados['data_emissao'] ?? date('Y-m-d H:i:s');
                $this->log("✅ Número normalizado: " . $dados['numero']);
                $this->log("✅ Série normalizada: " . $dados['serie']);

                // FORÇAR USO DOS DADOS ORIGINAIS
                $dados['numero_original'] = $dados['nfe']['numero'];
                $dados['serie_original'] = $dados['nfe']['serie'];
            }

            // Buscar certificado digital
            $this->log("🔐 Buscando certificado digital...");
            try {
                $certificado = $this->buscarCertificadoDigital($dados['empresa']);
                if (!$certificado) {
                    $this->log("❌ Certificado digital retornou null");
                    throw new \Exception("Certificado digital não encontrado");
                }
                $this->log("✅ Certificado digital carregado com sucesso");
            } catch (\Exception $e) {
                $this->log("❌ Erro ao buscar certificado digital: " . $e->getMessage());
                error_log("NFeService::gerarXMLReal - Erro ao buscar certificado: " . $e->getMessage());
                error_log("NFeService::gerarXMLReal - Stack trace: " . $e->getTraceAsString());
                throw $e;
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $dados['empresa']['nome'],
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj']),
                'siglaUF' => $dados['empresa']['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'verAplic' => '1.0', // CORREÇÃO: Tag verAplic preenchida
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Aumentado para 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Aumentado para 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura (3 minutos)
                'soap_write_timeout' => 180, // Timeout de escrita (3 minutos)
                'soap_user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'soap_headers' => [
                    'Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
                    'Accept-Language: pt-BR,pt;q=0.8,en;q=0.3',
                    'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
                    'Keep-Alive: 300',
                    'Connection: keep-alive'
                ],
                // CORREÇÕES TÉCNICAS PARA ERRO 202
                'soap_force_curl' => true,
                'soap_force_ipv4' => true,
                'soap_ssl_verify' => false,
                'soap_ssl_verify_peer' => false,
                'soap_ssl_verify_peer_name' => false,
                'soap_ssl_cipher_list' => 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA',
                // CORREÇÕES PARA NOTA TÉCNICA 2025.001 - MODO SÍNCRONO OBRIGATÓRIO
                'soap_ssl_version' => CURL_SSLVERSION_TLSv1_2,
                'soap_ssl_certificate_authority' => 'AC SOLUTI SSL EV G4',
                'soap_ssl_verify_host' => false,
                'soap_ssl_verify_peer_name' => false,
                'soap_ssl_certificate_path' => __DIR__ . '/../../certificados/empresas/',
                'soap_ssl_ca_bundle' => __DIR__ . '/../../certificados/ca-bundle.pem',
                // NOTA TÉCNICA 2025.001: MODO SÍNCRONO OBRIGATÓRIO PARA UMA NFe
                'soap_sync_mode' => true,
                'soap_async_mode' => false,
                'soap_lote_size' => 1
            ];

            $configJson = json_encode($config);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // USAR EXATAMENTE O MESMO MÉTODO DO SISTEMA QUE FUNCIONA
            $this->log("🔧 Gerando XML REAL da NFe usando NFePHP Make");

            $nfe = new \NFePHP\NFe\Make();

            // Detectar se é Simples Nacional (CSOSN) para definir CRT correto
            // Primeiro, verificar se há informação de regime tributário na empresa
            $crt = '3'; // Padrão: Regime Normal

            // Log dos dados recebidos para debug
            $regimeTributario = $dados['empresa']['regime_tributario'] ?? null;
            $optanteSimples = $dados['empresa']['optante_simples'] ?? null;

            $this->log("🔍 Detecção de CRT:");
            $this->log("  - regime_tributario: " . ($regimeTributario !== null ? var_export($regimeTributario, true) : 'NULL'));
            $this->log("  - optante_simples: " . ($optanteSimples !== null ? var_export($optanteSimples, true) : 'NULL'));
            error_log("🔍 Detecção de CRT - regime_tributario: " . ($regimeTributario !== null ? var_export($regimeTributario, true) : 'NULL') . ", optante_simples: " . ($optanteSimples !== null ? var_export($optanteSimples, true) : 'NULL'));

            // Verificar se empresa tem informação de regime tributário
            if ($regimeTributario !== null || $optanteSimples !== null) {
                // Se for optante do Simples Nacional, CRT = 1
                // Comparar com '1' ou 'Simples Nacional' (case insensitive)
                $ehSimplesNacional = false;

                if ($optanteSimples !== null && $optanteSimples !== '' && $optanteSimples !== '0' && $optanteSimples !== 0) {
                    $optanteSimplesStr = is_string($optanteSimples) ? trim($optanteSimples) : (string) $optanteSimples;
                    $ehSimplesNacional = (
                        $optanteSimples === '1' ||
                        $optanteSimples === 1 ||
                        strtolower($optanteSimplesStr) === 'sim' ||
                        strtolower($optanteSimplesStr) === 'simples nacional' ||
                        strtolower($optanteSimplesStr) === 's'
                    );
                    if ($ehSimplesNacional) {
                        $this->log("✅ Simples Nacional detectado via optante_simples: " . var_export($optanteSimples, true));
                        error_log("✅ Simples Nacional detectado via optante_simples: " . var_export($optanteSimples, true));
                    } else {
                        $this->log("ℹ️ optante_simples não indica Simples Nacional: " . var_export($optanteSimples, true));
                        error_log("ℹ️ optante_simples não indica Simples Nacional: " . var_export($optanteSimples, true));
                    }
                }

                if (!$ehSimplesNacional && $regimeTributario !== null) {
                    $regimeTributarioStr = is_string($regimeTributario) ? trim($regimeTributario) : (string) $regimeTributario;
                    $ehSimplesNacional = (
                        $regimeTributario === '1' ||
                        $regimeTributario === 1 ||
                        strtoupper($regimeTributarioStr) === 'SIMPLES NACIONAL' ||
                        strtolower($regimeTributarioStr) === 'simples nacional'
                    );
                    if ($ehSimplesNacional) {
                        $this->log("✅ Simples Nacional detectado via regime_tributario: " . var_export($regimeTributario, true));
                        error_log("✅ Simples Nacional detectado via regime_tributario: " . var_export($regimeTributario, true));
                    }
                }

                if ($ehSimplesNacional) {
                    $crt = '1';
                    $this->log("✅ CRT definido como 1 (Simples Nacional)");
                    error_log("✅ CRT definido como 1 (Simples Nacional)");
                } else {
                    $this->log("ℹ️ CRT permanece como 3 (Regime Normal) - optante_simples: " . var_export($optanteSimples, true) . ", regime_tributario: " . var_export($regimeTributario, true));
                    error_log("ℹ️ CRT permanece como 3 (Regime Normal) - optante_simples: " . var_export($optanteSimples, true) . ", regime_tributario: " . var_export($regimeTributario, true));
                }
            } else {
                $this->log("⚠️ Nenhum dado de regime tributário encontrado - CRT permanece como 3 (Regime Normal)");
                error_log("⚠️ Nenhum dado de regime tributário encontrado - CRT permanece como 3 (Regime Normal)");
            }

            // IMPORTANTE: Se não detectou pelo cadastro, verificar pelos itens (CSOSN)
            // Se houver CSOSN nos itens, a empresa DEVE ser Simples Nacional (CRT=1)
            // A SEFAZ rejeita CSOSN com CRT diferente de 1
            if ($crt == '3') {
                // Verificar se algum item usa CSOSN (Simples Nacional)
                $temCSOSN = false;
                foreach ($dados['itens'] as $item) {
                    $cstIcms = $item['cst_icms'] ?? '';
                    if (in_array($cstIcms, ['101', '102', '103', '201', '202', '203', '300', '400', '500', '900'])) {
                        $temCSOSN = true;
                        $this->log("⚠️ CSOSN encontrado nos itens ({$cstIcms}) mas CRT=3 - ajustando para CRT=1");
                        error_log("⚠️ CSOSN encontrado nos itens ({$cstIcms}) mas CRT=3 - ajustando para CRT=1");
                        break;
                    }
                }

                if ($temCSOSN) {
                    // Se há CSOSN, a empresa DEVE ser Simples Nacional
                    $crt = '1';
                    $this->log("✅ CRT ajustado para 1 (Simples Nacional) porque há CSOSN nos itens");
                    error_log("✅ CRT ajustado para 1 (Simples Nacional) porque há CSOSN nos itens");
                } else {
                    $this->log("✅ Regime detectado: Regime Normal (CRT=3)");
                    error_log("CRT detectado como 3 (Regime Normal)");
                }
            }

            // NÃO definir IE como ISENTO automaticamente aqui
            // A validação será feita mais abaixo, após detectar o CRT corretamente

            // Configurar dados básicos
            $nfe->taginfNFe((object) [
                'versao' => '4.00'
            ]);

            // DETECTAR SE É CONSUMIDOR FINAL (CPF ou sem Inscrição Estadual)
            $documentoCliente = preg_replace('/\D/', '', $dados['cliente']['cnpj'] ?? $dados['cliente']['cpf'] ?? '');
            $isCPF = strlen($documentoCliente) === 11;

            // Verificar se cliente não tem IE (rg_ie vazio = não contribuinte e consumidor final)
            $ieCliente = trim($dados['cliente']['inscricao_estadual'] ?? '');
            $semInscricaoEstadual = empty($ieCliente);

            // Se não tem IE, é automaticamente consumidor final e não contribuinte
            $isConsumidorFinal = $isCPF || $semInscricaoEstadual;

            $this->log("🔍 Detecção de Consumidor Final:");
            $this->log("  - Documento: " . ($isCPF ? 'CPF' : 'CNPJ') . " ({$documentoCliente})");
            $this->log("  - IE do cliente: " . ($ieCliente ?: 'VAZIA'));
            $this->log("  - Sem IE: " . ($semInscricaoEstadual ? 'SIM' : 'NÃO'));

            if ($isConsumidorFinal) {
                $this->log("✅ Cliente é CONSUMIDOR FINAL (indFinal=1, indIEDest=9)");
            } else {
                $this->log("✅ Cliente é CONTRIBUINTE ICMS (indFinal=0, indIEDest=1)");
            }

            // IDE
            $nfe->tagide((object) [
                'cUF' => $this->getCodigoUF($dados['empresa']['estado']),
                'cNF' => str_pad($dados['numero'] + 1000, 8, '0', STR_PAD_LEFT),
                'natOp' => 'Venda de mercadorias',
                'mod' => '55',
                'serie' => (int) $dados['serie'],
                'nNF' => $dados['numero'],
                'dhEmi' => date('c', strtotime($dados['data_emissao'])),
                'dhSaiEnt' => date('c', strtotime($dados['data_saida'] ?? $dados['data_emissao'])),
                'tpNF' => '1',
                'idDest' => '1',
                'cMunFG' => $this->getCodigoMunicipio($dados['empresa']['cidade'], $dados['empresa']['estado']),
                'tpImp' => '1',
                'tpEmis' => '1',
                'tpAmb' => '1',
                'finNFe' => '1',
                'indFinal' => $isConsumidorFinal ? '1' : '0', // 1 = Consumidor Final, 0 = Normal
                'indPres' => '1',
                'procEmi' => '0',
                'verProc' => '1.0'
            ]);

            // EMIT - CRT detectado automaticamente
            // SOLUÇÃO DEFINITIVA: IE do emitente é OBRIGATÓRIA - sempre deve ser enviada
            $ie = trim($dados['empresa']['inscricao_estadual'] ?? '');

            // LOG DIRETO NO ERROR_LOG PARA DEBUG
            error_log("=== VALIDAÇÃO IE EMITENTE ===");
            error_log("IE recebida: " . ($ie ?: 'VAZIA'));
            error_log("CRT detectado: " . $crt);
            error_log("Dados empresa: " . json_encode([
                'inscricao_estadual' => $dados['empresa']['inscricao_estadual'] ?? 'NULL',
                'cnpj' => $dados['empresa']['cnpj'] ?? 'NULL'
            ]));

            $this->log("🔍 Validação IE do Emitente:");
            $this->log("  - IE recebida do banco: " . ($ie ?: 'VAZIA'));
            $this->log("  - CRT detectado: " . $crt);

            // SOLUÇÃO DEFINITIVA: Processar IE de forma determinística
            // IMPORTANTE: Só definir como ISENTO se realmente for Simples Nacional E não houver IE válida

            // 1. Se IE tiver valor, limpar e validar PRIMEIRO (prioridade para IE válida)
            if (!empty($ie) && trim($ie) !== '' && strtoupper(trim($ie)) !== 'ISENTO') {
                // Limpar IE (remover caracteres não numéricos)
                $ieLimpa = preg_replace('/\D/', '', $ie);

                if (!empty($ieLimpa) && strlen($ieLimpa) >= 9) {
                    // IE válida (mínimo 9 dígitos) - USAR ESTA
                    $ie = $ieLimpa;
                    $this->log("✅ IE válida encontrada e será usada: {$ie}");
                    error_log("✅ IE válida encontrada: {$ie} (CRT: {$crt})");
                } else {
                    // IE inválida após limpeza - tratar como vazia
                    $this->log("⚠️ IE inválida após limpeza (menos de 9 dígitos), será tratada como vazia");
                    $ie = '';
                }
            }

            // 2. Se IE estiver vazia ou null APÓS validação
            if (empty($ie) || $ie === null || $ie === '' || strtoupper(trim($ie)) === 'ISENTO') {
                if ($crt === '1') {
                    // Simples Nacional pode ser ISENTO APENAS se realmente não tiver IE
                    $ie = 'ISENTO';
                    $this->log("✅ IE vazia/inválida → definida como ISENTO (Simples Nacional - CRT=1)");
                    error_log("✅ IE definida como ISENTO (CRT=1, Simples Nacional)");
                } else {
                    // Outros regimes OBRIGAM IE válida
                    $this->log("❌ ERRO: IE obrigatória para regime diferente de Simples Nacional (CRT={$crt})");
                    error_log("❌ ERRO: IE obrigatória para CRT={$crt}. IE recebida: " . ($dados['empresa']['inscricao_estadual'] ?? 'VAZIA'));
                    throw new \Exception("Inscrição Estadual do emitente é obrigatória para empresas que não são do Simples Nacional. Por favor, cadastre a IE da empresa no cadastro de empresas (Configurações > Empresas).");
                }
            }

            // 3. Se IE for "ISENTO" mas CRT não for 1, ERRO
            if (strtoupper(trim($ie)) === 'ISENTO' && $crt !== '1') {
                $this->log("❌ ERRO: Empresa não pode ser ISENTA se não for Simples Nacional (CRT={$crt})");
                error_log("❌ ERRO: IE=ISENTO mas CRT={$crt} (deveria ser 1)");
                throw new \Exception("Empresa não pode ser ISENTA de IE se não for Simples Nacional. Por favor, cadastre a IE correta da empresa no cadastro de empresas (Configurações > Empresas).");
            }

            // GARANTIA FINAL: IE nunca pode ser vazia, null ou string vazia
            if (empty($ie) || $ie === null || $ie === '' || strlen(trim($ie)) === 0) {
                if ($crt === '1') {
                    $ie = 'ISENTO';
                    $this->log("🔧 CORREÇÃO FINAL: IE forçada para ISENTO (Simples Nacional)");
                } else {
                    $this->log("❌ ERRO CRÍTICO: IE não pode ser vazia para CRT={$crt}!");
                    error_log("❌ ERRO CRÍTICO: IE vazia para CRT={$crt}");
                    throw new \Exception("ERRO CRÍTICO: Inscrição Estadual do emitente não foi definida. Por favor, cadastre a IE da empresa no cadastro de empresas (Configurações > Empresas).");
                }
            }

            // Preparar dados do emitente
            $emitData = [
                'CNPJ' => preg_replace('/\D/', '', $dados['empresa']['cnpj'] ?? ''),
                'xNome' => $dados['empresa']['nome'] ?? '',
                'xFant' => $dados['empresa']['nome'] ?? '',
                'IE' => $ie, // SEMPRE definida (nunca vazia)
                'CRT' => $crt
            ];

            $this->log("📤 Dados do Emitente (FINAL):");
            $this->log("  - CNPJ: " . $emitData['CNPJ']);
            $this->log("  - IE: '" . $emitData['IE'] . "' (tipo: " . gettype($emitData['IE']) . ", tamanho: " . strlen($emitData['IE']) . ")");
            $this->log("  - CRT: " . $emitData['CRT']);

            // VALIDAÇÃO FINAL ABSOLUTA
            if (empty($emitData['IE']) || $emitData['IE'] === null || $emitData['IE'] === '' || strlen(trim($emitData['IE'])) === 0) {
                $this->log("❌ ERRO CRÍTICO ABSOLUTO: IE ainda está vazia após TODAS as validações!");
                error_log("ERRO CRÍTICO NFe: IE do emitente vazia. CRT: {$crt}, IE recebida: " . ($dados['empresa']['inscricao_estadual'] ?? 'NULL'));
                throw new \Exception("ERRO CRÍTICO: Inscrição Estadual do emitente não foi definida corretamente. Verifique os logs do servidor.");
            }

            // LOG FINAL ANTES DE ENVIAR
            error_log("=== ANTES DE tagemit ===");
            error_log("IE final: '" . $emitData['IE'] . "'");
            error_log("Tipo: " . gettype($emitData['IE']));
            error_log("Tamanho: " . strlen($emitData['IE']));
            error_log("EmitData completo: " . json_encode($emitData));

            // Enviar para NFePHP
            try {
                $nfe->tagemit((object) $emitData);
                error_log("✅ tagemit executada com sucesso");
                $this->log("✅ tagemit executada com sucesso. IE enviada: '" . $emitData['IE'] . "'");
            } catch (\Exception $e) {
                error_log("❌ ERRO em tagemit: " . $e->getMessage());
                error_log("Dados enviados: " . json_encode($emitData));
                throw $e;
            }

            // ENDERECO EMIT
            $nfe->tagenderEmit((object) [
                'xLgr' => $dados['empresa']['endereco'],
                'nro' => $dados['empresa']['numero'],
                'xBairro' => $dados['empresa']['bairro'],
                'cMun' => $this->getCodigoMunicipio($dados['empresa']['cidade'], $dados['empresa']['estado']),
                'xMun' => $dados['empresa']['cidade'],
                'UF' => $dados['empresa']['estado'],
                'CEP' => preg_replace('/\D/', '', $dados['empresa']['cep']),
                'cPais' => '1058',
                'xPais' => 'Brasil',
                'fone' => !empty($dados['empresa']['telefone']) ? preg_replace('/\D/', '', $dados['empresa']['telefone']) : null
            ]);

            // DEST - Usar detecção já feita anteriormente
            $destData = [
                'xNome' => $dados['cliente']['nome'],
                'indIEDest' => $isConsumidorFinal ? '9' : '1', // 9 = Não Contribuinte, 1 = Contribuinte ICMS
            ];

            // Adicionar CPF ou CNPJ conforme o caso
            if ($isCPF) {
                $destData['CPF'] = $documentoCliente;
                $this->log("✅ Documento do cliente: CPF {$documentoCliente}");
            } else {
                $destData['CNPJ'] = $documentoCliente;
                // Só adiciona IE se tiver (contribuinte)
                if (!$semInscricaoEstadual) {
                    $destData['IE'] = $dados['cliente']['inscricao_estadual'];
                    $this->log("✅ Documento do cliente: CNPJ {$documentoCliente} com IE");
                } else {
                    $this->log("✅ Documento do cliente: CNPJ {$documentoCliente} SEM IE (não contribuinte)");
                }
            }

            $nfe->tagdest((object) $destData);

            // ENDERECO DEST
            // Verificar se já existe código do município nos dados do cliente
            $codigoMunicipioDest = null;
            if (!empty($dados['cliente']['codigo_municipio']) || !empty($dados['cliente']['city_code'])) {
                $codigoMunicipioDest = $dados['cliente']['codigo_municipio'] ?? $dados['cliente']['city_code'];
                $this->log("✅ Usando código do município fornecido nos dados do cliente: {$codigoMunicipioDest}");
            } else {
                // Buscar código do município baseado na cidade e UF
                $codigoMunicipioDest = $this->getCodigoMunicipio($dados['cliente']['cidade'], $dados['cliente']['estado']);
                $this->log("✅ Código do município buscado: {$codigoMunicipioDest} para {$dados['cliente']['cidade']}/{$dados['cliente']['estado']}");
            }

            // Validar que o código corresponde à UF informada
            if (!empty($codigoMunicipioDest) && strlen($codigoMunicipioDest) === 7) {
                $ufInformada = strtoupper(trim($dados['cliente']['estado']));
                $codigoValido = $this->validarCodigoMunicipioUF($codigoMunicipioDest, $ufInformada);

                if (!$codigoValido) {
                    $this->log("⚠️ AVISO: Código do município {$codigoMunicipioDest} não corresponde à UF {$ufInformada}. Buscando código correto...");
                    // Buscar código correto baseado na cidade e UF
                    $codigoMunicipioDest = $this->getCodigoMunicipio($dados['cliente']['cidade'], $dados['cliente']['estado']);

                    // Validar novamente o código obtido
                    if (!empty($codigoMunicipioDest)) {
                        $codigoValido = $this->validarCodigoMunicipioUF($codigoMunicipioDest, $ufInformada);
                        if (!$codigoValido) {
                            $this->log("❌ ERRO: Não foi possível obter um código de município válido para {$dados['cliente']['cidade']}/{$ufInformada}");
                            // Se ainda não for válido, usar código padrão ou lançar erro
                            throw new \Exception("Código do município inválido para a UF informada. Verifique os dados do cliente.");
                        }
                    }
                }
            } else {
                // Se não há código, buscar baseado na cidade e UF
                $codigoMunicipioDest = $this->getCodigoMunicipio($dados['cliente']['cidade'], $dados['cliente']['estado']);
            }

            $nfe->tagenderDest((object) [
                'xLgr' => $dados['cliente']['endereco'],
                'nro' => 'S/N',
                'xBairro' => $dados['cliente']['bairro'],
                'cMun' => $codigoMunicipioDest,
                'xMun' => $dados['cliente']['cidade'],
                'UF' => $dados['cliente']['estado'],
                'CEP' => preg_replace('/\D/', '', $dados['cliente']['cep']),
                'cPais' => '1058',
                'xPais' => 'Brasil'
            ]);

            // Adicionar itens usando NFePHP\NFe\Make
            foreach ($dados['itens'] as $index => $item) {
                $nItem = $index + 1;

                $nfe->tagprod((object) [
                    'item' => $nItem,
                    'cProd' => $item['codigo'],
                    'cEAN' => 'SEM GTIN',
                    'xProd' => $item['descricao'],
                    'NCM' => $item['ncm'] ?? '00000000', // NCM do banco de dados
                    'CFOP' => $item['cfop'],
                    'uCom' => $item['unidade_comercial'],
                    'qCom' => number_format($item['quantidade_comercial'], 4, '.', ''),
                    'vUnCom' => number_format($item['valor_unitario'], 4, '.', ''),
                    'vProd' => number_format($item['valor_total'], 2, '.', ''),
                    'cEANTrib' => 'SEM GTIN',
                    'uTrib' => $item['unidade_comercial'],
                    'qTrib' => number_format($item['quantidade_comercial'], 4, '.', ''),
                    'vUnTrib' => number_format($item['valor_unitario'], 4, '.', ''),
                    'indTot' => '1'
                ]);

                $nfe->tagimposto((object) [
                    'item' => $nItem,
                    'vTotTrib' => number_format($item['valor_icms'] + ($item['valor_ipi'] ?? 0) + $item['valor_pis'] + $item['valor_cofins'], 2, '.', '')
                ]);

                // Calcular ICMS corretamente
                $baseCalculo = $item['base_calculo_icms'];
                $aliquota = $item['aliquota_icms'];
                $valorICMS = ($baseCalculo * $aliquota) / 100;

                // Detectar se é CSOSN (Simples Nacional) ou CST (Regime Normal)
                $cstIcms = $item['cst_icms'];
                $csosn = in_array($cstIcms, ['101', '102', '103', '201', '202', '203', '300', '400', '500', '900']);

                if ($csosn) {
                    // CSOSN - Simples Nacional
                    $icmsData = (object) [
                        'item' => $nItem,
                        'orig' => $item['origem'],
                        'CSOSN' => $cstIcms
                    ];

                    // CSOSN 101 - Tributada pelo Simples Nacional com permissão de crédito
                    if ($cstIcms == '101') {
                        $icmsData->pCredSN = number_format($aliquota, 2, '.', '');
                        $icmsData->vCredICMSSN = number_format($valorICMS, 2, '.', '');
                    }
                    // CSOSN 102, 103, 300, 400 - Sem permissão de crédito
                    // Não precisa de campos adicionais

                    // CSOSN 201 - Tributada pelo Simples Nacional com permissão de crédito e com cobrança do ICMS por ST
                    elseif ($cstIcms == '201') {
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                        $icmsData->pCredSN = number_format($aliquota, 2, '.', '');
                        $icmsData->vCredICMSSN = number_format($valorICMS, 2, '.', '');
                    }
                    // CSOSN 202, 203 - Com ST mas sem permissão de crédito
                    elseif (in_array($cstIcms, ['202', '203'])) {
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                    }
                    // CSOSN 500 - ICMS cobrado anteriormente por ST ou por antecipação
                    elseif ($cstIcms == '500') {
                        $icmsData->vBCSTRet = '0.00';
                        $icmsData->pST = '0.00';
                        $icmsData->vICMSSTRet = '0.00';
                    }
                    // CSOSN 900 - Outros
                    elseif ($cstIcms == '900') {
                        $icmsData->modBC = '3';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pRedBC = '0.00';
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                        $icmsData->pCredSN = number_format($aliquota, 2, '.', '');
                        $icmsData->vCredICMSSN = number_format($valorICMS, 2, '.', '');
                    }

                    // Usar tagICMSSN para Simples Nacional
                    $nfe->tagICMSSN($icmsData);
                } else {
                    // CST - Regime Normal (Lucro Real/Presumido)
                    $icmsData = (object) [
                        'item' => $nItem,
                        'orig' => $item['origem'],
                        'CST' => $cstIcms
                    ];

                    // CST 00 - Tributada integralmente
                    if ($cstIcms == '00') {
                        $icmsData->modBC = '3';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                    }
                    // CST 10 - Tributada com cobrança do ICMS por ST
                    elseif ($cstIcms == '10') {
                        $icmsData->modBC = '3';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                    }
                    // CST 20 - Com redução de base de cálculo
                    elseif ($cstIcms == '20') {
                        $icmsData->modBC = '3';
                        $icmsData->pRedBC = '0.00';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                    }
                    // CST 30 - Isenta ou não tributada e com cobrança do ICMS por ST
                    elseif ($cstIcms == '30') {
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                    }
                    // CST 40/41/50 - Isenta, não tributada ou suspensão
                    elseif (in_array($cstIcms, ['40', '41', '50'])) {
                        // CST 40/41/50 não precisa de campos adicionais
                    }
                    // CST 51 - Diferimento
                    elseif ($cstIcms == '51') {
                        $icmsData->modBC = '3';
                        $icmsData->pRedBC = '0.00';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMSOp = '0.00';
                        $icmsData->pDif = '100.00';
                        $icmsData->vICMSDif = '0.00';
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                    }
                    // CST 60 - ICMS cobrado anteriormente por ST
                    elseif ($cstIcms == '60') {
                        $icmsData->vBCSTRet = '0.00';
                        $icmsData->pST = '0.00';
                        $icmsData->vICMSSTRet = '0.00';
                    }
                    // CST 90 - Outros
                    elseif ($cstIcms == '90') {
                        $icmsData->modBC = '3';
                        $icmsData->vBC = number_format($baseCalculo, 2, '.', '');
                        $icmsData->pRedBC = '0.00';
                        $icmsData->pICMS = number_format($aliquota, 2, '.', '');
                        $icmsData->vICMS = number_format($valorICMS, 2, '.', '');
                        $icmsData->modBCST = '4';
                        $icmsData->pMVAST = '0.00';
                        $icmsData->pRedBCST = '0.00';
                        $icmsData->vBCST = '0.00';
                        $icmsData->pICMSST = '0.00';
                        $icmsData->vICMSST = '0.00';
                    }

                    // Usar tagICMS para Regime Normal
                    $nfe->tagICMS($icmsData);
                }

                // Adicionar PIS - O NFePHP cria a tag correta baseada no CST
                $nfe->tagPIS((object) [
                    'item' => $nItem,
                    'CST' => $item['cst_pis'],
                    'vBC' => number_format($item['base_calculo_pis'], 2, '.', ''),
                    'pPIS' => number_format($item['aliquota_pis'], 2, '.', ''),
                    'vPIS' => number_format($item['valor_pis'], 2, '.', '')
                ]);

                // Adicionar COFINS - O NFePHP cria a tag correta baseada no CST
                $nfe->tagCOFINS((object) [
                    'item' => $nItem,
                    'CST' => $item['cst_cofins'],
                    'vBC' => number_format($item['base_calculo_cofins'], 2, '.', ''),
                    'pCOFINS' => number_format($item['aliquota_cofins'], 2, '.', ''),
                    'vCOFINS' => number_format($item['valor_cofins'], 2, '.', '')
                ]);

                // Adicionar IBS/CBS - Nova tributação usando método nativo do NFePHP
                // IMPORTANTE: Sempre adicionar IBSCBS se houver configuração no banco (mesmo valores zerados)
                // Verificar se há campos de IBS/CBS no item (array_key_exists verifica se a chave existe no array)
                $temDadosIBSCBS = (
                    array_key_exists('cclass', $item) ||
                    array_key_exists('aliquota_ibs', $item) ||
                    array_key_exists('aliquota_cbs', $item) ||
                    array_key_exists('aliquota_ibs_municipal', $item)
                );

                // Log detalhado para debug
                $this->log("🔍 Item {$nItem}: Verificando IBSCBS");
                $this->log("  - cclass existe: " . (array_key_exists('cclass', $item) ? 'SIM (' . ($item['cclass'] ?? 'NULL') . ')' : 'NÃO'));
                $this->log("  - aliquota_ibs existe: " . (array_key_exists('aliquota_ibs', $item) ? 'SIM (' . ($item['aliquota_ibs'] ?? 'NULL') . ')' : 'NÃO'));
                $this->log("  - aliquota_cbs existe: " . (array_key_exists('aliquota_cbs', $item) ? 'SIM (' . ($item['aliquota_cbs'] ?? 'NULL') . ')' : 'NÃO'));
                $this->log("  - temDadosIBSCBS: " . ($temDadosIBSCBS ? 'SIM' : 'NÃO'));

                error_log("🔍 Item {$nItem}: Verificando IBSCBS - cclass: " . ($item['cclass'] ?? 'N/A') . ", aliquota_ibs: " . ($item['aliquota_ibs'] ?? 0) . ", aliquota_cbs: " . ($item['aliquota_cbs'] ?? 0) . ", temDadosIBSCBS: " . ($temDadosIBSCBS ? 'SIM' : 'NÃO'));

                if ($temDadosIBSCBS) {
                    $this->log("✅ Item {$nItem}: Configuração IBSCBS encontrada - processando...");
                    error_log("✅ Item {$nItem}: Configuração IBSCBS encontrada - processando...");
                    // Calcular valores de IBS/CBS
                    $baseCalculo = floatval($item['valor_total'] ?? 0);
                    $aliquotaIbs = floatval($item['aliquota_ibs'] ?? 0);
                    $reducaoIbs = floatval($item['reducao_aliquota_ibs'] ?? 0);
                    $aliquotaCbs = floatval($item['aliquota_cbs'] ?? 0);
                    $reducaoCbs = floatval($item['reducao_aliquota_cbs'] ?? 0);
                    $aliquotaIbsMun = floatval($item['aliquota_ibs_municipal'] ?? 0);
                    $reducaoIbsMun = floatval($item['reducao_aliquota_ibs_municipal'] ?? 0);

                    // Calcular alíquotas efetivas (com redução)
                    $aliquotaIbsEfetiva = max(0, $aliquotaIbs - $reducaoIbs);
                    $aliquotaCbsEfetiva = max(0, $aliquotaCbs - $reducaoCbs);
                    $aliquotaIbsMunEfetiva = max(0, $aliquotaIbsMun - $reducaoIbsMun);

                    // Calcular valores
                    $vIBS = ($baseCalculo * $aliquotaIbsEfetiva) / 100;
                    $vCBS = ($baseCalculo * $aliquotaCbsEfetiva) / 100;
                    $vIBSMun = ($baseCalculo * $aliquotaIbsMunEfetiva) / 100;
                    $vIBSUF = $vIBS - $vIBSMun; // IBS UF = IBS Total - IBS Municipal

                    // Percentuais (dividir por 100 para converter de % para decimal)
                    $pIBSUF = $aliquotaIbsEfetiva > 0 ? ($vIBSUF / $baseCalculo) * 100 : 0;
                    $pCBS = $aliquotaCbsEfetiva;
                    $pIBSMun = $aliquotaIbsMunEfetiva;

                    // IMPORTANTE: O NFePHP só cria o grupo gIBSCBS se vBC não for null e for numérico
                    // E vBC precisa ser > 0 para criar o grupo completo
                    // Se vBC for 0, ainda precisamos enviar vBC como 0 para criar a estrutura

                    // Usar método nativo do NFePHP para adicionar IBSCBS ao item
                    $dadosIBSCBS = [
                        'item' => $nItem,
                        'CST' => '000',
                        'cClassTrib' => !empty($item['cclass']) ? $item['cclass'] : '000001',
                        'vBC' => $baseCalculo > 0 ? $baseCalculo : 0.01, // Garantir que seja > 0 para criar o grupo
                        'gIBSUF_pIBSUF' => max(0, $pIBSUF),
                        'gIBSUF_vIBSUF' => max(0, $vIBSUF),
                        'gIBSMun_pIBSMun' => max(0, $pIBSMun),
                        'gIBSMun_vIBSMun' => max(0, $vIBSMun),
                        'vIBS' => max(0, $vIBS),
                        'gCBS_pCBS' => max(0, $pCBS),
                        'gCBS_vCBS' => max(0, $vCBS)
                    ];

                    $this->log("📝 Item {$nItem}: Dados IBSCBS antes de chamar tagIBSCBS: " . json_encode($dadosIBSCBS));
                    error_log("📝 Item {$nItem}: Dados IBSCBS antes de chamar tagIBSCBS: " . json_encode($dadosIBSCBS));

                    try {
                        $resultado = $nfe->tagIBSCBS((object) $dadosIBSCBS);
                        $this->log("✅ Item {$nItem}: tagIBSCBS chamado com sucesso. Resultado: " . get_class($resultado));
                        error_log("✅ Item {$nItem}: tagIBSCBS chamado com sucesso - Resultado: " . get_class($resultado));
                    } catch (\Exception $e) {
                        $this->log("❌ Item {$nItem}: ERRO ao chamar tagIBSCBS: " . $e->getMessage());
                        error_log("❌ Item {$nItem}: ERRO ao chamar tagIBSCBS: " . $e->getMessage());
                        error_log("❌ Stack trace: " . $e->getTraceAsString());
                        throw $e; // Quebrar o fluxo para identificar o problema
                    }

                    // Armazenar dados para cálculo do total
                    if (!isset($this->dadosIBSCBS)) {
                        $this->dadosIBSCBS = [];
                    }
                    $this->dadosIBSCBS[$nItem] = [
                        'vBC' => $baseCalculo,
                        'vIBS' => $vIBS,
                        'vIBSUF' => $vIBSUF,
                        'vIBSMun' => $vIBSMun,
                        'vCBS' => $vCBS
                    ];
                }
            }

            // Calcular totais
            $totalProdutos = array_sum(array_column($dados['itens'], 'valor_total'));

            // Verificar se algum item usa CSOSN (Simples Nacional)
            $temCSOSN = false;
            foreach ($dados['itens'] as $item) {
                $cstIcms = $item['cst_icms'];
                if (in_array($cstIcms, ['101', '102', '103', '201', '202', '203', '300', '400', '500', '900'])) {
                    $temCSOSN = true;
                    break;
                }
            }

            // Log detalhado dos itens para debug
            $this->log("📊 DEBUG - Analisando itens para cálculo dos totais:");
            foreach ($dados['itens'] as $index => $item) {
                $this->log("  Item " . ($index + 1) . ":");
                $this->log("    - Descrição: " . ($item['descricao'] ?? 'N/A'));
                $this->log("    - Valor Total: " . ($item['valor_total'] ?? 'N/A'));
                $this->log("    - Base Cálculo ICMS: " . ($item['base_calculo_icms'] ?? 'N/A'));
                $this->log("    - Alíquota ICMS: " . ($item['aliquota_icms'] ?? 'N/A') . "%");
                $this->log("    - Valor ICMS: " . ($item['valor_icms'] ?? 'N/A'));

                // Validar consistência
                $baseCalc = floatval($item['base_calculo_icms'] ?? 0);
                $aliquota = floatval($item['aliquota_icms'] ?? 0);
                $valorIcms = floatval($item['valor_icms'] ?? 0);
                $valorEsperado = $baseCalc * ($aliquota / 100);

                if (abs($valorIcms - $valorEsperado) > 0.02 && $baseCalc > 0 && $aliquota > 0) {
                    $this->log("    ⚠️ INCONSISTÊNCIA! Valor ICMS esperado: " . number_format($valorEsperado, 2, '.', ''));
                }
            }

            // Calcular totais de ICMS considerando apenas itens que realmente têm ICMS tributado
            // Não incluir: CSOSN 101, 102, 103, 201, 202, 203, 300, 400, 500 (sem vICMS)
            // Não incluir: CST 30, 40, 41, 50, 60 (isentos ou sem ICMS)
            // Incluir apenas: CSOSN 900 (tem vICMS), CST 00, 10, 20, 51, 90 (têm vICMS)
            $totalICMS = 0;
            $totalBaseICMS = 0;

            foreach ($dados['itens'] as $item) {
                $cstIcms = $item['cst_icms'] ?? '';
                $csosn = in_array($cstIcms, ['101', '102', '103', '201', '202', '203', '300', '400', '500', '900']);

                // Verificar se este item deve ter ICMS no total
                $temICMSTotal = false;

                if ($csosn) {
                    // CSOSN 900 tem vICMS, os outros não
                    if ($cstIcms == '900') {
                        $temICMSTotal = true;
                    }
                } else {
                    // CST - Regime Normal
                    // CST 00, 10, 20, 51, 90 têm vICMS
                    // CST 30, 40, 41, 50, 60 não têm vICMS
                    if (in_array($cstIcms, ['00', '10', '20', '51', '90'])) {
                        $temICMSTotal = true;
                    }
                }

                if ($temICMSTotal) {
                    $valorIcms = floatval($item['valor_icms'] ?? 0);
                    $baseCalc = floatval($item['base_calculo_icms'] ?? 0);
                    $totalICMS += $valorIcms;
                    $totalBaseICMS += $baseCalc;
                    $this->log("  ✅ Item incluído no total ICMS: CST/CSOSN=$cstIcms | vBC=$baseCalc | vICMS=$valorIcms");
                } else {
                    $this->log("  ⏭️ Item excluído do total ICMS: CST/CSOSN=$cstIcms (não tem vICMS)");
                }
            }

            $this->log("💰 Totais ICMS: vBC=$totalBaseICMS | vICMS=$totalICMS");

            // Validar se os totais fazem sentido
            if ($totalBaseICMS > 0 && $totalICMS > $totalBaseICMS) {
                $this->log("⚠️ ALERTA: Valor do ICMS ($totalICMS) é maior que a base de cálculo ($totalBaseICMS)!");
                $this->log("⚠️ Isso causará rejeição 532 da SEFAZ!");
            }

            $totalIPI = array_sum(array_map(function ($item) {
                return $item['valor_ipi'] ?? 0;
            }, $dados['itens']));
            $totalPIS = array_sum(array_column($dados['itens'], 'valor_pis'));
            $totalCOFINS = array_sum(array_column($dados['itens'], 'valor_cofins'));

            // Calcular totais de IBSCBS
            $totalVBCIBSCBS = 0;
            $totalVIBS = 0;
            $totalVIBSUF = 0;
            $totalVIBSMun = 0;
            $totalVCBS = 0;

            foreach ($dados['itens'] as $item) {
                if (!empty($item['cclass']) || !empty($item['aliquota_ibs']) || !empty($item['aliquota_cbs'])) {
                    $baseCalculo = floatval($item['valor_total'] ?? 0);
                    $aliquotaIbs = floatval($item['aliquota_ibs'] ?? 0);
                    $reducaoIbs = floatval($item['reducao_aliquota_ibs'] ?? 0);
                    $aliquotaCbs = floatval($item['aliquota_cbs'] ?? 0);
                    $reducaoCbs = floatval($item['reducao_aliquota_cbs'] ?? 0);
                    $aliquotaIbsMun = floatval($item['aliquota_ibs_municipal'] ?? 0);
                    $reducaoIbsMun = floatval($item['reducao_aliquota_ibs_municipal'] ?? 0);

                    // Calcular alíquotas efetivas
                    $aliquotaIbsEfetiva = max(0, $aliquotaIbs - $reducaoIbs);
                    $aliquotaCbsEfetiva = max(0, $aliquotaCbs - $reducaoCbs);
                    $aliquotaIbsMunEfetiva = max(0, $aliquotaIbsMun - $reducaoIbsMun);

                    // Calcular valores
                    $vIBS = ($baseCalculo * $aliquotaIbsEfetiva) / 100;
                    $vCBS = ($baseCalculo * $aliquotaCbsEfetiva) / 100;
                    $vIBSMun = ($baseCalculo * $aliquotaIbsMunEfetiva) / 100;
                    $vIBSUF = $vIBS - $vIBSMun;

                    // Acumular totais
                    $totalVBCIBSCBS += $baseCalculo;
                    $totalVIBS += $vIBS;
                    $totalVIBSUF += $vIBSUF;
                    $totalVIBSMun += $vIBSMun;
                    $totalVCBS += $vCBS;
                }
            }

            // Adicionar totais usando NFePHP\NFe\Make
            $nfe->tagICMSTot((object) [
                'vBC' => number_format($totalBaseICMS, 2, '.', ''),
                'vICMS' => number_format($totalICMS, 2, '.', ''),
                'vICMSDeson' => '0.00',
                'vFCP' => '0.00',
                'vBCST' => '0.00',
                'vST' => '0.00',
                'vFCPST' => '0.00',
                'vFCPSTRet' => '0.00',
                'vProd' => number_format($totalProdutos, 2, '.', ''),
                'vFrete' => '0.00',
                'vSeg' => '0.00',
                'vDesc' => '0.00',
                'vII' => '0.00',
                'vIPI' => number_format($totalIPI, 2, '.', ''),
                'vIPIDevol' => '0.00',
                'vPIS' => number_format($totalPIS, 2, '.', ''),
                'vCOFINS' => number_format($totalCOFINS, 2, '.', ''),
                'vOutro' => '0.00',
                'vNF' => number_format($totalProdutos, 2, '.', '')
            ]);

            // Adicionar IBSCBSTot usando método nativo do NFePHP se houver valores
            if ($totalVBCIBSCBS > 0 || $totalVIBS > 0 || $totalVCBS > 0) {
                $this->log("💰 Totais IBSCBS: vBC=$totalVBCIBSCBS | vIBS=$totalVIBS | vIBSUF=$totalVIBSUF | vIBSMun=$totalVIBSMun | vCBS=$totalVCBS");

                // Usar método nativo do NFePHP para adicionar IBSCBSTot
                $nfe->tagIBSCBSTot((object) [
                    'vBCIBSCBS' => $totalVBCIBSCBS,
                    'gIBS_vIBS' => $totalVIBS,
                    'gIBSUF_vIBSUF' => $totalVIBSUF,
                    'gIBSMun_vIBSMun' => $totalVIBSMun,
                    'gCBS_vCBS' => $totalVCBS,
                    'gIBSUF_vDif' => 0,
                    'gIBSUF_vDevTrib' => 0,
                    'gIBSMun_vDif' => 0,
                    'gIBSMun_vDevTrib' => 0,
                    'gCBS_vDif' => 0,
                    'gCBS_vDevTrib' => 0,
                    'gIBS_vCredPres' => 0,
                    'gIBS_vCredPresCondSus' => 0,
                    'gCBS_vCredPres' => 0,
                    'gCBS_vCredPresCondSus' => 0,
                    'gEstonoCred_vIBSEstCred' => 0,
                    'gEstonoCred_vCBSEstCred' => 0
                ]);

                // Armazenar para referência (não será mais usado para inserção manual)
                $this->dadosIBSCBSTot = [
                    'vBCIBSCBS' => $totalVBCIBSCBS,
                    'vIBS' => $totalVIBS,
                    'vIBSUF' => $totalVIBSUF,
                    'vIBSMun' => $totalVIBSMun,
                    'vCBS' => $totalVCBS
                ];
            }

            // Adicionar transporte
            $nfe->tagtransp((object) [
                'modFrete' => '9'
            ]);

            // Adicionar pagamento
            $nfe->tagpag((object) [
                'vPag' => number_format($totalProdutos, 2, '.', ''),
                'tPag' => '99',
                'indPag' => '0'
            ]);

            // Adicionar detalhe do pagamento
            $nfe->tagdetPag((object) [
                'tPag' => '99',
                'vPag' => number_format($totalProdutos, 2, '.', ''),
                'xPag' => 'Pagamento via PIX'
            ]);

            // Adicionar informações do responsável técnico
            $nfe->taginfRespTec((object) [
                'CNPJ' => '30305332000145',
                'xContato' => 'Systhema Tecnologia',
                'email' => 'desenvolvimento@Systhema.com.br',
                'fone' => '81997102197'
            ]);

            // Adicionar observações NFe se existirem
            if (!empty($dados['nfe']['observacoes'])) {
                $stdInfAdic = new \stdClass();
                $stdInfAdic->infAdFisco = null; // opcional
                $stdInfAdic->infCpl = $dados['nfe']['observacoes'];
                $nfe->taginfAdic($stdInfAdic);
                $this->log("📝 Observações NFe adicionadas: " . substr($dados['nfe']['observacoes'], 0, 100) . "...");
            }

            // Verificar erros antes de gerar XML
            $errors = $nfe->getErrors();
            if (!empty($errors)) {
                error_log("❌ Erros encontrados no XML: " . json_encode($errors));
                throw new \Exception("Erros nas tags do XML: " . implode(', ', $errors));
            }

            // Gerar XML usando NFePHP\NFe\Make
            // O NFePHP já adiciona IBSCBSTot automaticamente se tagIBSCBSTot foi chamado
            $xml = $nfe->getXML();

            // Limpar dados de IBSCBS dos itens (não usamos mais para inserção manual)
            if (!empty($this->dadosIBSCBS)) {
                $this->dadosIBSCBS = [];
            }

            // Limpar dados de IBSCBSTot (não usamos mais para inserção manual)
            if (!empty($this->dadosIBSCBSTot)) {
                $this->dadosIBSCBSTot = [];
            }

            error_log("✅ XML REAL gerado com NFePHP\\NFe\\Make");
            return $xml;
        } catch (\Exception $e) {
            error_log("❌ Erro ao gerar XML real: " . $e->getMessage());
            throw new \Exception("Erro ao gerar XML: " . $e->getMessage());
        }
    }

    /**
     * Assina XML REAL usando certificado digital
     */
    public function assinarXMLReal($xml, $empresa)
    {
        try {
            $this->log("🔐 ASSINANDO XML REAL");

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($empresa);
            if (!$certificado) {
                throw new \Exception("Certificado digital não encontrado");
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $empresa['nome'],
                'cnpj' => preg_replace('/\D/', '', $empresa['cnpj']),
                'siglaUF' => $empresa['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'verAplic' => '1.0', // CORREÇÃO: Tag verAplic preenchida
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Aumentado para 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Aumentado para 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura (3 minutos)
                'soap_write_timeout' => 180, // Timeout de escrita (3 minutos)
                'soap_user_agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
                'soap_headers' => [
                    'Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
                    'Accept-Language: pt-BR,pt;q=0.8,en;q=0.3',
                    'Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7',
                    'Keep-Alive: 300',
                    'Connection: keep-alive'
                ],
                // CORREÇÕES TÉCNICAS PARA ERRO 202
                'soap_force_curl' => true,
                'soap_force_ipv4' => true,
                'soap_ssl_verify' => false,
                'soap_ssl_verify_peer' => false,
                'soap_ssl_verify_peer_name' => false,
                'soap_ssl_cipher_list' => 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA',
                // CORREÇÕES PARA NOTA TÉCNICA 2025.001 - MODO SÍNCRONO OBRIGATÓRIO
                'soap_ssl_version' => CURL_SSLVERSION_TLSv1_2,
                'soap_ssl_certificate_authority' => 'AC SOLUTI SSL EV G4',
                'soap_ssl_verify_host' => false,
                'soap_ssl_verify_peer_name' => false,
                'soap_ssl_certificate_path' => __DIR__ . '/../../certificados/empresas/',
                'soap_ssl_ca_bundle' => __DIR__ . '/../../certificados/ca-bundle.pem',
                // NOTA TÉCNICA 2025.001: MODO SÍNCRONO OBRIGATÓRIO PARA UMA NFe
                'soap_sync_mode' => true,
                'soap_async_mode' => false,
                'soap_lote_size' => 1
            ];

            $configJson = json_encode($config);
            $this->log("🔧 Configuração: " . $configJson);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // Log do XML antes da assinatura
            $this->log("📄 XML antes da assinatura: " . substr($xml, 0, 500) . "...");

            // Verificar se o XML contém tpAmb correto
            if (strpos($xml, '<tpAmb>1</tpAmb>') !== false) {
                $this->log("✅ XML contém tpAmb=1 (produção)");
            } else {
                $this->log("❌ XML NÃO contém tpAmb=1! Conteúdo: " . substr($xml, 0, 1000));
            }

            // Assinar XML
            $xmlAssinado = $tools->signNFe($xml);

            // Log do XML após assinatura
            $this->log("📄 XML após assinatura: " . substr($xmlAssinado, 0, 500) . "...");

            // Verificar se a assinatura foi adicionada
            if (strpos($xmlAssinado, '<Signature') !== false) {
                $this->log("✅ Assinatura digital encontrada no XML");
            } else {
                $this->log("❌ Assinatura digital NÃO encontrada no XML!");
            }

            $this->log("✅ XML REAL assinado com sucesso");
            return $xmlAssinado;
        } catch (\Exception $e) {
            error_log("❌ Erro ao assinar XML: " . $e->getMessage());
            throw new \Exception("Erro ao assinar XML: " . $e->getMessage());
        }
    }

    /**
     * Envia para SEFAZ REAL - IGUAL AO SISTEMA QUE FUNCIONA
     */
    private function enviarParaSEFAZReal($xml, $dados)
    {
        try {
            // Aumentar limite de tempo de execução para comunicação com SEFAZ
            set_time_limit(240); // 4 minutos para dar margem ao timeout de 180s
            ini_set('max_execution_time', '240');

            // Configurar timeouts do PHP para SOAP e sockets (a biblioteca NFePHP pode usar esses valores)
            ini_set('default_socket_timeout', '180'); // Timeout padrão de sockets
            ini_set('soap.wsdl_cache_enabled', '0'); // Desabilitar cache WSDL
            ini_set('soap.wsdl_cache_ttl', '0');

            // Log das configurações atuais do PHP
            $maxExecutionTime = ini_get('max_execution_time');
            $socketTimeout = ini_get('default_socket_timeout');
            $this->logSefaz("🚀 ENVIANDO PARA SEFAZ REAL", [
                'chave_nfe' => $this->extrairChaveAcessoDoXML($xml) ?? 'N/A',
                'empresa_cnpj' => preg_replace('/[^0-9]/', '', $dados['empresa']['cnpj'])
            ]);

            $this->logSefaz("⏱️ Timeouts configurados", [
                'php_max_execution_time' => $maxExecutionTime . 's (configurado: 240s)',
                'php_default_socket_timeout' => $socketTimeout . 's (configurado: 180s)',
                'soap_timeout' => '180s',
                'soap_connection_timeout' => '90s',
                'soap_read_timeout' => '180s',
                'soap_write_timeout' => '180s'
            ]);

            // Manter error_log também para compatibilidade
            error_log("🚀 ENVIANDO PARA SEFAZ REAL");
            error_log("⏱️ Timeouts configurados:");
            error_log("   - PHP max_execution_time: {$maxExecutionTime}s (configurado: 240s)");
            error_log("   - PHP default_socket_timeout: {$socketTimeout}s (configurado: 180s)");
            error_log("   - SOAP timeout: 180s");
            error_log("   - SOAP connection timeout: 90s");

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($dados['empresa']);
            if (!$certificado) {
                throw new \Exception("Certificado digital não encontrado");
            }

            // Configuração para SEFAZ - IGUAL AO SISTEMA QUE FUNCIONA
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $dados['empresa']['nome'],
                'cnpj' => preg_replace('/[^0-9]/', '', $dados['empresa']['cnpj']),
                'ie' => preg_replace('/[^0-9]/', '', $dados['empresa']['inscricao_estadual']),
                'siglaUF' => $dados['empresa']['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Aumentado para 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Aumentado para 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura (3 minutos)
                'soap_write_timeout' => 180, // Timeout de escrita (3 minutos)
                'soap_force_curl' => true,
                'soap_force_ipv4' => true,
                // Configurações adicionais para garantir que os timeouts sejam respeitados
                'soap_curl_timeout' => 180,
                'soap_curl_connect_timeout' => 90
            ];

            $configJson = json_encode($config);
            error_log("📋 Configuração JSON: " . substr($configJson, 0, 500) . "...");

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // CRÍTICO: Configurar timeout diretamente no objeto SOAP após criação
            // A biblioteca NFePHP usa timeout padrão de 20s, precisamos forçar o timeout
            // Criar instância do SoapCurl com timeout configurado e injetar no Tools
            $soap = new \NFePHP\Common\Soap\SoapCurl($certificado);
            $soap->timeout(180); // Configurar timeout para 180 segundos (timeout total)
            // IMPORTANTE: O timeout de conexão também precisa ser aumentado
            // A biblioteca usa soaptimeout para CURLOPT_CONNECTTIMEOUT também
            $tools->loadSoapClass($soap);
            $this->logSefaz("✅ Timeout configurado diretamente no objeto SOAP: 180s", [
                'timeout_configurado' => 180,
                'timeout_conexao' => 180,
                'timeout_total' => 200, // soaptimeout + 20
                'metodo' => 'loadSoapClass'
            ]);
            error_log("✅ Timeout configurado diretamente no objeto SOAP: 180s (conexão) / 200s (total)");

            // Diagnóstico: Verificar conectividade básica com a SEFAZ
            $sefazHost = 'nfe.sefaz.pe.gov.br';
            $sefazPort = 443;
            $this->logSefaz("🔍 Verificando conectividade com SEFAZ", [
                'host' => $sefazHost,
                'porta' => $sefazPort
            ]);

            // Teste rápido de conectividade (timeout de 5 segundos)
            $connection = @fsockopen($sefazHost, $sefazPort, $errno, $errstr, 5);
            if ($connection) {
                fclose($connection);
                $this->logSefaz("✅ Conectividade básica OK com SEFAZ");
                error_log("✅ Conectividade básica OK com SEFAZ");
            } else {
                $this->logSefaz("⚠️ Problema de conectividade detectado", [
                    'erro_numero' => $errno,
                    'mensagem_erro' => $errstr,
                    'host' => $sefazHost,
                    'porta' => $sefazPort
                ]);
                error_log("⚠️ Problema de conectividade: [$errno] $errstr");
                // Não bloquear, apenas logar - pode ser firewall temporário
            }

            // Gerar ID do lote (máximo 15 dígitos)
            $idLote = date('YmdHis');

            $this->logSefaz("📤 Enviando lote para SEFAZ", ['id_lote' => $idLote]);
            $tempoInicio = microtime(true);
            $this->logSefaz("⏱️ Tempo de início", [
                'data_hora' => date('Y-m-d H:i:s'),
                'timestamp' => $tempoInicio
            ]);

            // Manter error_log também para compatibilidade
            error_log("📤 Enviando lote para SEFAZ: " . $idLote);
            error_log("⏱️ Tempo de início: " . date('Y-m-d H:i:s') . " (timestamp: " . $tempoInicio . ")");

            // Enviar para SEFAZ com modo SÍNCRONO (indSinc = 1) quando houver apenas 1 NF-e
            // Isso evita o erro 452: "Solicitada resposta assíncrona para Lote com somente 1 (uma) NF-e"
            try {
                $response = $tools->sefazEnviaLote([$xml], $idLote, 1);
                $tempoFim = microtime(true);
                $tempoDecorrido = round($tempoFim - $tempoInicio, 2);
                $this->logSefaz("✅ Resposta recebida", [
                    'tempo_decorrido_segundos' => $tempoDecorrido,
                    'id_lote' => $idLote
                ]);
                error_log("✅ Resposta recebida após {$tempoDecorrido} segundos");
            } catch (\Exception $e) {
                $tempoFim = microtime(true);
                $tempoDecorrido = round($tempoFim - $tempoInicio, 2);
                $this->logSefaz("❌ Erro na comunicação com SEFAZ", [
                    'tempo_decorrido_segundos' => $tempoDecorrido,
                    'mensagem_erro' => $e->getMessage(),
                    'tipo_erro' => get_class($e),
                    'codigo_erro' => $e->getCode(),
                    'stack_trace' => $e->getTraceAsString(),
                    'id_lote' => $idLote
                ]);
                error_log("❌ Erro após {$tempoDecorrido} segundos: " . $e->getMessage());
                error_log("❌ Tipo de erro: " . get_class($e));
                error_log("❌ Stack trace: " . $e->getTraceAsString());
                throw $e;
            }

            error_log("📥 Resposta SEFAZ: " . json_encode($response));

            // Padronizar resposta - IGUAL AO SISTEMA QUE FUNCIONA
            $st = new \NFePHP\NFe\Common\Standardize($response);
            $std = $st->toStd();

            // Verificar o código de status do lote
            // cStat 103 = Lote recebido (modo assíncrono - precisa consultar depois)
            // cStat 104 = Lote processado (modo síncrono - já vem com o resultado)
            if ($std->cStat == 103) {
                // Modo ASSÍNCRONO: Lote recebido, precisa consultar recibo
                $nRec = $std->infRec->nRec;
                error_log("✅ Lote recebido pela SEFAZ (assíncrono). nRec: " . $nRec);

                // Consultar protocolo
                error_log("🔍 Aguardando processamento do lote...");
                sleep(3); // Aguarda 3 segundos antes de consultar

                $protocoloResponse = $tools->sefazConsultaRecibo($nRec);
                error_log("📋 Recibo consultado com sucesso");

                // Padronizar resposta do protocolo
                $st = new \NFePHP\NFe\Common\Standardize($protocoloResponse);
                $std = $st->toStd();
            } elseif ($std->cStat == 104) {
                // Modo SÍNCRONO: Lote já processado, resposta já contém o resultado
                error_log("✅ Lote processado pela SEFAZ (síncrono). cStat: 104");
                // $std já contém a resposta completa, não precisa consultar recibo
            } else {
                // Erro no processamento do lote
                $chaveAcesso = $this->extrairChaveAcessoDoXML($xml);
                $cStat = $std->cStat;
                $motivo = $std->xMotivo ?? 'Erro desconhecido';

                // Se houver nRec na resposta, incluir na mensagem
                $nRecInfo = isset($std->infRec->nRec) ? " [nRec: " . $std->infRec->nRec . "]" : "";

                $errorMsg = "Rejeição: " . $motivo . $nRecInfo;

                // Tratamento específico para erro 539 (duplicidade com diferença na chave)
                if ($cStat == '539' || $cStat == 539) {
                    $this->logSefaz("⚠️ Erro 539: Duplicidade de NF-e com diferença na Chave de Acesso", [
                        'chave_nfe_tentada' => $chaveAcesso,
                        'numero' => $dados['numero_original'] ?? $dados['numero'],
                        'serie' => $dados['serie_original'] ?? $dados['serie'],
                        'nRec' => $std->infRec->nRec ?? null,
                        'motivo_completo' => $motivo
                    ]);

                    $errorMsg = 'Erro 539: Duplicidade de NF-e detectada. ' .
                        'Já existe uma NF-e com o número ' . ($dados['numero_original'] ?? $dados['numero']) . ' e série ' . ($dados['serie_original'] ?? $dados['serie']) . ' na SEFAZ, ' .
                        'mas com uma chave de acesso diferente. ' .
                        'SOLUÇÃO: Use o próximo número disponível. ' .
                        'O sistema deve buscar automaticamente o último número de NF-e autorizado pela empresa e usar o próximo número sequencial. ' .
                        'Verifique no cadastro da empresa qual é o último número de NF-e emitido.' . $nRecInfo;
                }

                // Tratamento específico para erro 204 (duplicidade comum)
                if ($cStat == '204' || $cStat == 204) {
                    $this->logSefaz("⚠️ Erro 204: Duplicidade de NF-e", [
                        'chave_nfe_tentada' => $chaveAcesso,
                        'numero' => $dados['numero_original'] ?? $dados['numero'],
                        'serie' => $dados['serie_original'] ?? $dados['serie'],
                        'nRec' => $std->infRec->nRec ?? null
                    ]);

                    $errorMsg = 'Erro 204: Duplicidade de NF-e. ' .
                        'Já existe uma NF-e autorizada com o número ' . ($dados['numero_original'] ?? $dados['numero']) . ' e série ' . ($dados['serie_original'] ?? $dados['serie']) . '. ' .
                        'SOLUÇÃO: Use o próximo número disponível. ' .
                        'Verifique no cadastro da empresa qual é o último número de NF-e emitido e use o próximo número sequencial.' . $nRecInfo;
                }

                error_log("❌ Erro ao enviar lote [" . $cStat . "]: " . $errorMsg);
                error_log("   Retornando response com cStat: " . $cStat);

                $errorResponse = [
                    'success' => false,
                    'chave_acesso' => $chaveAcesso,
                    'cStat' => $cStat,
                    'numero' => $dados['numero_original'] ?? $dados['numero'],
                    'serie' => $dados['serie_original'] ?? $dados['serie'],
                    'error' => $errorMsg
                ];

                error_log("   Response: " . json_encode($errorResponse));
                return $errorResponse;
            }

            // Log completo da resposta para debug
            error_log("📋 Resposta completa da SEFAZ: " . json_encode($std));

            // Verificar se foi autorizada
            if (
                $std->cStat == 104 &&
                isset($std->protNFe->infProt->cStat) &&
                in_array($std->protNFe->infProt->cStat, [100, 150])
            ) {

                $protocoloNumero = $std->protNFe->infProt->nProt;
                $cStat = $std->protNFe->infProt->cStat;
                $chaveAcesso = $this->extrairChaveAcessoDoXML($xml);

                error_log("✅ NFe autorizada! Protocolo: " . $protocoloNumero . " | cStat: " . $cStat);

                // Criar XML protocolado manualmente
                $xmlProtocolado = $this->criarXMLProtocoladoManualmente($xml, $std->protNFe);

                return [
                    'success' => true,
                    'chave_acesso' => $chaveAcesso,
                    'numero' => $dados['numero_original'] ?? $dados['numero'],
                    'serie' => $dados['serie_original'] ?? $dados['serie'],
                    'protocolo' => $protocoloNumero,
                    'cStat' => $cStat,
                    'xml_protocolado' => $xmlProtocolado, // XML com protocolo anexado
                    'protocolo_response' => $response // Resposta completa do protocolo
                ];
            } else {
                // NFe rejeitada - capturar informações de diferentes lugares possíveis
                $cStat = null;
                $motivo = 'Motivo não informado';
                $chaveAcesso = $this->extrairChaveAcessoDoXML($xml);

                // Tentar diferentes caminhos para obter cStat e motivo
                if (isset($std->protNFe->infProt->cStat)) {
                    $cStat = $std->protNFe->infProt->cStat;
                    $motivo = $std->protNFe->infProt->xMotivo ?? $motivo;
                } elseif (isset($std->cStat)) {
                    $cStat = $std->cStat;
                    $motivo = $std->xMotivo ?? $motivo;
                }

                // Log detalhado do erro
                error_log("❌ NFe rejeitada!");
                error_log("   cStat extraído: " . ($cStat ?? 'null'));
                error_log("   Motivo: " . $motivo);
                error_log("   Estrutura completa: " . json_encode($std));
                error_log("   Retornando cStat: " . ($cStat ?? 'NULL'));

                // Tratamento específico para erros conhecidos
                $errorMsg = 'NFe rejeitada [' . ($cStat ?? 'N/A') . ']: ' . $motivo;

                // Erro 539: Duplicidade de NF-e com diferença na Chave de Acesso
                if ($cStat == '539' || $cStat == 539) {
                    $this->logSefaz("⚠️ Erro 539: Duplicidade de NF-e detectada", [
                        'chave_nfe_tentada' => $chaveAcesso,
                        'numero' => $dados['numero_original'] ?? $dados['numero'],
                        'serie' => $dados['serie_original'] ?? $dados['serie'],
                        'motivo_completo' => $motivo
                    ]);

                    $errorMsg = 'Erro 539: Duplicidade de NF-e detectada. ' .
                        'Já existe uma NF-e com o número ' . ($dados['numero_original'] ?? $dados['numero']) . ' e série ' . ($dados['serie_original'] ?? $dados['serie']) . ' na SEFAZ, ' .
                        'mas com uma chave de acesso diferente. ' .
                        'SOLUÇÃO: Use o próximo número disponível. ' .
                        'O sistema deve buscar automaticamente o último número de NF-e autorizado pela empresa e usar o próximo número sequencial. ' .
                        'Verifique no cadastro da empresa qual é o último número de NF-e emitido.';
                }

                // Erro 204: Duplicidade de NF-e (mais comum)
                if ($cStat == '204' || $cStat == 204) {
                    $this->logSefaz("⚠️ Erro 204: Duplicidade de NF-e", [
                        'chave_nfe_tentada' => $chaveAcesso,
                        'numero' => $dados['numero_original'] ?? $dados['numero'],
                        'serie' => $dados['serie_original'] ?? $dados['serie']
                    ]);

                    $errorMsg = 'Erro 204: Duplicidade de NF-e. ' .
                        'Já existe uma NF-e autorizada com o número ' . ($dados['numero_original'] ?? $dados['numero']) . ' e série ' . ($dados['serie_original'] ?? $dados['serie']) . '. ' .
                        'SOLUÇÃO: Use o próximo número disponível. ' .
                        'Verifique no cadastro da empresa qual é o último número de NF-e emitido e use o próximo número sequencial.';
                }

                $errorResponse = [
                    'success' => false,
                    'chave_acesso' => $chaveAcesso,
                    'cStat' => $cStat,
                    'numero' => $dados['numero_original'] ?? $dados['numero'],
                    'serie' => $dados['serie_original'] ?? $dados['serie'],
                    'error' => $errorMsg
                ];

                error_log("   Response final: " . json_encode($errorResponse));
                return $errorResponse;
            }
        } catch (\Exception $e) {
            $mensagemErro = $e->getMessage();

            // Tentar extrair chave de acesso mesmo em caso de erro
            $chaveAcesso = null;
            try {
                $chaveAcesso = $this->extrairChaveAcessoDoXML($xml);
            } catch (\Exception $ex) {
                // Ignorar erro ao extrair chave
            }

            $this->logSefaz("❌ Erro ao enviar para SEFAZ", [
                'mensagem_erro' => $mensagemErro,
                'tipo_excecao' => get_class($e),
                'codigo_erro' => $e->getCode(),
                'chave_nfe' => $chaveAcesso,
                'stack_trace' => $e->getTraceAsString()
            ]);

            error_log("❌ Erro ao enviar para SEFAZ: " . $mensagemErro);
            error_log("❌ Tipo de exceção: " . get_class($e));
            error_log("❌ Código do erro: " . $e->getCode());

            // Verificar se é timeout específico
            $isTimeout = false;
            $timeoutDetectado = false;
            if (
                stripos($mensagemErro, 'timeout') !== false ||
                stripos($mensagemErro, 'timed out') !== false ||
                stripos($mensagemErro, 'Connection timed out') !== false ||
                stripos($mensagemErro, 'Operation timed out') !== false
            ) {
                $isTimeout = true;
                $timeoutDetectado = true;
                $this->logSefaz("⚠️ TIMEOUT DETECTADO na mensagem de erro", [
                    'chave_nfe' => $chaveAcesso,
                    'mensagem_erro' => $mensagemErro
                ]);
                error_log("⚠️ TIMEOUT DETECTADO na mensagem de erro");
            }

            // Verificar código de erro cURL (28 = timeout)
            if ($e->getCode() == 28 || (method_exists($e, 'getPrevious') && $e->getPrevious() && $e->getPrevious()->getCode() == 28)) {
                $isTimeout = true;
                $timeoutDetectado = true;
                $this->logSefaz("⚠️ TIMEOUT DETECTADO via código de erro cURL (28)", [
                    'chave_nfe' => $chaveAcesso,
                    'codigo_erro' => $e->getCode()
                ]);
                error_log("⚠️ TIMEOUT DETECTADO via código de erro cURL (28)");
            }

            // Tentar extrair cStat da mensagem de erro
            $cStat = null;
            if (preg_match('/\[(\d+)\]:/', $mensagemErro, $matches)) {
                $cStat = $matches[1];
            }

            // Se for timeout, personalizar mensagem baseado no tipo de erro
            if ($isTimeout) {
                // Verificar se é erro de conexão (não conseguiu conectar) vs timeout de resposta
                $isConnectionError = stripos($mensagemErro, 'Failed to connect') !== false ||
                    stripos($mensagemErro, 'Could not connect') !== false ||
                    stripos($mensagemErro, 'Connection refused') !== false;

                if ($isConnectionError) {
                    $mensagemErro = 'Erro de conexão com a SEFAZ de PE. O servidor não conseguiu estabelecer conexão com nfe.sefaz.pe.gov.br na porta 443. ' .
                        'Possíveis causas: ' .
                        '1) Firewall do servidor bloqueando conexões HTTPS (porta 443) ' .
                        '2) SEFAZ bloqueando o IP do servidor ' .
                        '3) Problemas de rede/DNS no servidor ' .
                        '4) Proxy ou configurações de rede impedindo a conexão. ' .
                        'SOLUÇÃO: Verifique com o administrador do servidor se a porta 443 está liberada para saída e se o IP do servidor não está bloqueado pela SEFAZ. ' .
                        'Teste manual: Execute no servidor: telnet nfe.sefaz.pe.gov.br 443 ou curl -v https://nfe.sefaz.pe.gov.br';
                } else {
                    $mensagemErro = 'Timeout na comunicação com a SEFAZ. A conexão demorou mais de 180 segundos (3 minutos) para responder. ' .
                        'Possíveis causas: SEFAZ sobrecarregada, problemas de rede ou firewall bloqueando a conexão. ' .
                        'Tente novamente em alguns instantes. Se o problema persistir, verifique a conectividade do servidor com a SEFAZ de PE. ' .
                        'Em caso de persistência, entre em contato com o suporte técnico.';
                }
            }

            return [
                'success' => false,
                'chave_acesso' => $chaveAcesso,
                'cStat' => $cStat,
                'numero' => $dados['numero_original'] ?? $dados['numero'],
                'serie' => $dados['serie_original'] ?? $dados['serie'],
                'error' => $mensagemErro,
                'is_timeout' => $isTimeout,
                'error_code' => $e->getCode(),
                'error_class' => get_class($e)
            ];
        }
    }

    /**
     * Busca certificado digital - IGUAL AO SISTEMA QUE FUNCIONA
     */
    private function buscarCertificadoDigital($empresa)
    {
        try {
            // Se veio certificado_path e senha via dados da empresa, usar eles
            // O campo no banco é 'senha_certificado', mas pode vir como 'certificado_senha' do array montado
            $senhaCertificado = $empresa['certificado_senha'] ?? $empresa['senha_certificado'] ?? '';

            if (!empty($empresa['certificado_path']) && !empty($senhaCertificado)) {
                $caminhoCertificado = $empresa['certificado_path'];

                // Se o caminho começa com /storage, converter para ROOT_PATH
                if (strpos($caminhoCertificado, '/storage') === 0 || strpos($caminhoCertificado, 'storage') === 0) {
                    $caminhoCertificado = str_replace('/storage', '', $caminhoCertificado);
                    $caminhoCertificado = ltrim($caminhoCertificado, '/');
                    $caminhoCertificado = \ROOT_PATH . '/storage/' . $caminhoCertificado;
                } elseif (strpos($caminhoCertificado, '/') === 0 && strpos($caminhoCertificado, \ROOT_PATH) !== 0) {
                    // Se começa com / mas não é um caminho absoluto completo, adicionar ROOT_PATH
                    $caminhoCertificado = \ROOT_PATH . $caminhoCertificado;
                }

                $this->log("🔐 Usando certificado da empresa: " . $caminhoCertificado);
                $this->log("🔐 Certificado existe? " . (file_exists($caminhoCertificado) ? 'SIM' : 'NÃO'));
            } else {
                // Fallback: caminho antigo (CNPJ baseado em diretório)
                $caminhoCertificado = __DIR__ . "/../../certificados/empresas/" . preg_replace('/\D/', '', $empresa['cnpj']) . "/" . preg_replace('/\D/', '', $empresa['cnpj']) . ".pfx";

                // Carregar configuração do certificado
                $configCert = require __DIR__ . "/../../config_certificado.php";
                $senhaCertificado = $configCert['senha_certificado'];

                $this->log("🔐 Usando certificado padrão: " . $caminhoCertificado);
            }

            if (!file_exists($caminhoCertificado)) {
                $this->log("❌ Certificado não encontrado no caminho: " . $caminhoCertificado);
                $this->log("❌ ROOT_PATH: " . (\ROOT_PATH ?? 'NÃO DEFINIDO'));
                error_log("NFeService::buscarCertificadoDigital - Certificado não encontrado: " . $caminhoCertificado);
                throw new \Exception("Certificado não encontrado: " . $caminhoCertificado);
            }

            // Usar helper centralizado (compatível com OpenSSL 3.x)
            $this->log("🔐 Carregando certificado usando CertificateHelper");
            $certificado = \App\Core\CertificateHelper::loadPfx($caminhoCertificado, $senhaCertificado);
            $this->log("✅ Certificado carregado com sucesso");
            return $certificado;
        } catch (\Exception $e) {
            error_log("❌ Erro ao carregar certificado: " . $e->getMessage());
            throw new \Exception("Erro ao carregar certificado: " . $e->getMessage());
        }
    }

    /**
     * Gera chave de acesso completa
     */
    private function gerarChaveAcessoCompleta($dados)
    {
        $uf = $this->getCodigoUF($dados['empresa']['estado']);
        $ano = date('y', strtotime($dados['data_emissao']));
        $mes = date('m', strtotime($dados['data_emissao']));
        $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
        $modelo = '55';
        $serie = str_pad($dados['serie'], 3, '0', STR_PAD_LEFT);
        $numero = str_pad($dados['numero'], 9, '0', STR_PAD_LEFT);
        $tipo = '1';
        $codigo = str_pad($dados['numero'], 8, '0', STR_PAD_LEFT);

        $chaveSemDV = $uf . $ano . $mes . $cnpj . $modelo . $serie . $numero . $tipo . $codigo;

        $dv = $this->calcularDigitoVerificador($chaveSemDV);

        return $chaveSemDV . $dv;
    }

    /**
     * Calcula dígito verificador
     */
    private function calcularDigitoVerificador($chave)
    {
        $soma = 0;
        $peso = 2;

        for ($i = strlen($chave) - 1; $i >= 0; $i--) {
            $soma += intval($chave[$i]) * $peso;
            $peso = $peso == 9 ? 2 : $peso + 1;
        }

        $resto = $soma % 11;
        return $resto < 2 ? 0 : 11 - $resto;
    }

    /**
     * Extrai chave de acesso do XML
     */
    private function extrairChaveAcessoDoXML($xml)
    {
        if (preg_match('/Id="NFe([0-9]{44})"/', $xml, $matches)) {
            return $matches[1];
        }
        return null;
    }

    /**
     * Obtém código da UF
     */
    private function getCodigoUF($uf)
    {
        $codigos = [
            'AC' => '12',
            'AL' => '27',
            'AP' => '16',
            'AM' => '13',
            'BA' => '29',
            'CE' => '23',
            'DF' => '53',
            'ES' => '32',
            'GO' => '52',
            'MA' => '21',
            'MT' => '51',
            'MS' => '50',
            'MG' => '31',
            'PA' => '15',
            'PB' => '25',
            'PR' => '41',
            'PE' => '26',
            'PI' => '22',
            'RJ' => '33',
            'RN' => '24',
            'RS' => '43',
            'RO' => '11',
            'RR' => '14',
            'SC' => '42',
            'SP' => '35',
            'SE' => '28',
            'TO' => '17'
        ];

        return $codigos[$uf] ?? '26'; // PE como padrão
    }

    /**
     * Obtém código do município
     */
    /**
     * Busca código IBGE do município baseado na cidade e UF
     * Valida que o código corresponde à UF informada para evitar erro 275
     */
    private function getCodigoMunicipio($cidade, $uf)
    {
        if (empty($cidade) || empty($uf)) {
            $this->log("⚠️ Cidade ou UF vazia para buscar código do município");
            return '2611606'; // Fallback para Recife
        }

        $uf = strtoupper(trim($uf));
        $cidade = strtoupper(trim($cidade));

        // Mapeamento de códigos IBGE comuns (expansível)
        $codigosMunicipios = [
            // PE - Pernambuco
            'PE' => [
                'RECIFE' => '2611606',
                'PAULISTA' => '2610707',
                'OLINDA' => '2609600',
                'JABOATÃO DOS GUARARAPES' => '2607901',
                'CAMARAGIBE' => '2603454',
                'CARUARU' => '2604106',
                'PETROLINA' => '2611101',
                'CABO DE SANTO AGOSTINHO' => '2602902',
            ],
            // SP - São Paulo
            'SP' => [
                'SÃO PAULO' => '3550308',
                'CAMPINAS' => '3509502',
                'GUARULHOS' => '3518800',
                'SÃO BERNARDO DO CAMPO' => '3548708',
                'SANTO ANDRÉ' => '3547809',
                'OSASCO' => '3534401',
                'RIBEIRÃO PRETO' => '3543402',
                'SOROCABA' => '3552205',
            ],
            // RJ - Rio de Janeiro
            'RJ' => [
                'RIO DE JANEIRO' => '3304557',
                'NITERÓI' => '3303302',
                'DUQUE DE CAXIAS' => '3301702',
                'NOVA IGUAÇU' => '3303500',
                'SÃO GONÇALO' => '3304904',
            ],
            // MG - Minas Gerais
            'MG' => [
                'BELO HORIZONTE' => '3106200',
                'UBERLÂNDIA' => '3170206',
                'CONTAGEM' => '3118601',
                'JUIZ DE FORA' => '3136702',
            ],
            // PR - Paraná
            'PR' => [
                'CURITIBA' => '4106902',
                'LONDRINA' => '4113700',
                'MARINGÁ' => '4115200',
                'PONTA GROSSA' => '4119905',
            ],
            // RS - Rio Grande do Sul
            'RS' => [
                'PORTO ALEGRE' => '4314902',
                'CAXIAS DO SUL' => '4305108',
                'PELOTAS' => '4314407',
                'CANOAS' => '4304606',
            ],
            // BA - Bahia
            'BA' => [
                'SALVADOR' => '2927408',
                'FEIRA DE SANTANA' => '2910800',
                'VITÓRIA DA CONQUISTA' => '2933307',
            ],
            // GO - Goiás
            'GO' => [
                'GOIÂNIA' => '5208707',
                'APARECIDA DE GOIÂNIA' => '5201405',
            ],
            // CE - Ceará
            'CE' => [
                'FORTALEZA' => '2304400',
                'CAUCAIA' => '2303709',
            ],
            // DF - Distrito Federal
            'DF' => [
                'BRASÍLIA' => '5300108',
            ],
        ];

        // Buscar código específico
        if (isset($codigosMunicipios[$uf][$cidade])) {
            $codigo = $codigosMunicipios[$uf][$cidade];
            $this->log("✅ Código IBGE encontrado: {$codigo} para {$cidade}/{$uf}");
            return $codigo;
        }

        // Tentar busca por similaridade (remover acentos, espaços extras, etc)
        $cidadeNormalizada = $this->normalizarNomeCidade($cidade);
        foreach ($codigosMunicipios[$uf] ?? [] as $nomeCidade => $codigo) {
            $nomeNormalizado = $this->normalizarNomeCidade($nomeCidade);
            if (
                $nomeNormalizado === $cidadeNormalizada ||
                strpos($nomeNormalizado, $cidadeNormalizada) !== false ||
                strpos($cidadeNormalizada, $nomeNormalizado) !== false
            ) {
                $this->log("✅ Código IBGE encontrado por similaridade: {$codigo} para {$cidade}/{$uf}");
                return $codigo;
            }
        }

        // Se não encontrou, usar API do IBGE para buscar
        $codigo = $this->buscarCodigoMunicipioIBGE($cidade, $uf);
        if ($codigo) {
            return $codigo;
        }

        // Fallback: retornar código baseado na UF (primeiro dígito do código IBGE)
        // Mas isso pode causar erro 275, então vamos lançar exceção ou log de erro
        $this->log("⚠️ ATENÇÃO: Código IBGE não encontrado para {$cidade}/{$uf}. Usando fallback.");
        $this->log("⚠️ Isso pode causar erro 275 se a UF estiver incorreta.");

        // Retornar código padrão baseado na UF (primeiro dígito do código)
        // Mas validar que pelo menos a UF está correta
        $ufMap = [
            'PE' => '2611606', // Recife
            'SP' => '3550308', // São Paulo
            'RJ' => '3304557', // Rio de Janeiro
            'MG' => '3106200', // Belo Horizonte
            'PR' => '4106902', // Curitiba
            'RS' => '4314902', // Porto Alegre
            'BA' => '2927408', // Salvador
            'GO' => '5208707', // Goiânia
            'CE' => '2304400', // Fortaleza
            'DF' => '5300108', // Brasília
        ];

        if (isset($ufMap[$uf])) {
            $this->log("⚠️ Usando código padrão da capital de {$uf}: {$ufMap[$uf]}");
            return $ufMap[$uf];
        }

        // Último fallback: Recife (mas logar aviso)
        $this->log("❌ ERRO: UF '{$uf}' não reconhecida. Usando código de Recife (2611606). Isso pode causar erro 275!");
        return '2611606';
    }

    /**
     * Valida se um código IBGE de município corresponde à UF informada
     * Nota: Esta validação é aproximada, pois os dois primeiros dígitos do código IBGE não correspondem diretamente à UF
     */
    private function validarCodigoMunicipioUF($codigoMunicipio, $uf)
    {
        // Validar formato básico
        if (strlen($codigoMunicipio) !== 7 || !ctype_digit($codigoMunicipio)) {
            return false;
        }

        $uf = strtoupper(trim($uf));

        // Mapeamento de faixas de códigos IBGE por UF
        // Os dois primeiros dígitos do código IBGE indicam o estado
        $faixasUF = [
            'AC' => ['12'], // Acre
            'AL' => ['27'], // Alagoas
            'AP' => ['16'], // Amapá
            'AM' => ['13'], // Amazonas
            'BA' => ['29'], // Bahia
            'CE' => ['23'], // Ceará
            'DF' => ['53'], // Distrito Federal
            'ES' => ['32'], // Espírito Santo
            'GO' => ['52'], // Goiás
            'MA' => ['21'], // Maranhão
            'MT' => ['51'], // Mato Grosso
            'MS' => ['50'], // Mato Grosso do Sul
            'MG' => ['31'], // Minas Gerais
            'PA' => ['15'], // Pará
            'PB' => ['25'], // Paraíba
            'PR' => ['41'], // Paraná
            'PE' => ['26'], // Pernambuco
            'PI' => ['22'], // Piauí
            'RJ' => ['33'], // Rio de Janeiro
            'RN' => ['24'], // Rio Grande do Norte
            'RS' => ['43'], // Rio Grande do Sul
            'RO' => ['11'], // Rondônia
            'RR' => ['14'], // Roraima
            'SC' => ['42'], // Santa Catarina
            'SP' => ['35'], // São Paulo
            'SE' => ['28'], // Sergipe
            'TO' => ['17'], // Tocantins
        ];

        // Verificar se a UF está no mapeamento
        if (!isset($faixasUF[$uf])) {
            $this->log("⚠️ UF '{$uf}' não encontrada no mapeamento de validação");
            return false;
        }

        // Pegar os dois primeiros dígitos do código
        $prefixo = substr($codigoMunicipio, 0, 2);

        // Verificar se o prefixo corresponde à UF
        if (in_array($prefixo, $faixasUF[$uf])) {
            return true;
        }

        // Se não corresponde, verificar se o código existe no mapeamento de municípios para essa UF
        // Isso garante que mesmo códigos fora da faixa padrão sejam validados se estiverem no mapeamento
        $codigoEncontrado = $this->verificarCodigoNoMapeamento($codigoMunicipio, $uf);
        if ($codigoEncontrado) {
            return true;
        }

        $this->log("❌ Código do município {$codigoMunicipio} (prefixo {$prefixo}) não corresponde à UF {$uf}");
        return false;
    }

    /**
     * Verifica se o código do município existe no mapeamento para a UF informada
     */
    private function verificarCodigoNoMapeamento($codigoMunicipio, $uf)
    {
        $uf = strtoupper(trim($uf));

        // Mapeamento de códigos IBGE (mesmo usado em getCodigoMunicipio)
        $codigosMunicipios = [
            'PE' => [
                'RECIFE' => '2611606',
                'PAULISTA' => '2610707',
                'OLINDA' => '2609600',
                'JABOATÃO DOS GUARARAPES' => '2607901',
                'CAMARAGIBE' => '2603454',
                'CARUARU' => '2604106',
                'PETROLINA' => '2611101',
                'CABO DE SANTO AGOSTINHO' => '2602902',
            ],
            'SP' => [
                'SÃO PAULO' => '3550308',
                'CAMPINAS' => '3509502',
                'GUARULHOS' => '3518800',
                'SÃO BERNARDO DO CAMPO' => '3548708',
                'SANTO ANDRÉ' => '3547809',
                'OSASCO' => '3534401',
                'RIBEIRÃO PRETO' => '3543402',
                'SOROCABA' => '3552205',
            ],
            'RJ' => [
                'RIO DE JANEIRO' => '3304557',
                'NITERÓI' => '3303302',
                'DUQUE DE CAXIAS' => '3301702',
                'NOVA IGUAÇU' => '3303500',
                'SÃO GONÇALO' => '3304904',
            ],
            'MG' => [
                'BELO HORIZONTE' => '3106200',
                'UBERLÂNDIA' => '3170206',
                'CONTAGEM' => '3118601',
                'JUIZ DE FORA' => '3136702',
            ],
            'PR' => [
                'CURITIBA' => '4106902',
                'LONDRINA' => '4113700',
                'MARINGÁ' => '4115200',
                'PONTA GROSSA' => '4119905',
            ],
            'RS' => [
                'PORTO ALEGRE' => '4314902',
                'CAXIAS DO SUL' => '4305108',
                'PELOTAS' => '4314407',
                'CANOAS' => '4304606',
            ],
            'BA' => [
                'SALVADOR' => '2927408',
                'FEIRA DE SANTANA' => '2910800',
                'VITÓRIA DA CONQUISTA' => '2933307',
            ],
            'GO' => [
                'GOIÂNIA' => '5208707',
                'APARECIDA DE GOIÂNIA' => '5201405',
            ],
            'CE' => [
                'FORTALEZA' => '2304400',
                'CAUCAIA' => '2303709',
            ],
            'DF' => [
                'BRASÍLIA' => '5300108',
            ],
        ];

        // Verificar se o código existe no mapeamento para essa UF
        if (isset($codigosMunicipios[$uf])) {
            return in_array($codigoMunicipio, $codigosMunicipios[$uf]);
        }

        return false;
    }

    /**
     * Normaliza nome da cidade para busca (remove acentos, espaços extras, etc)
     */
    private function normalizarNomeCidade($nome)
    {
        // Remove acentos
        $nome = $this->removeAcentos($nome);
        // Converter para maiúsculo
        $nome = strtoupper($nome);
        // Remover espaços extras
        $nome = preg_replace('/\s+/', ' ', trim($nome));
        return $nome;
    }

    /**
     * Remove acentos de uma string
     */
    private function removeAcentos($string)
    {
        $acentos = [
            'À',
            'Á',
            'Â',
            'Ã',
            'Ä',
            'à',
            'á',
            'â',
            'ã',
            'ä',
            'È',
            'É',
            'Ê',
            'Ë',
            'è',
            'é',
            'ê',
            'ë',
            'Ì',
            'Í',
            'Î',
            'Ï',
            'ì',
            'í',
            'î',
            'ï',
            'Ò',
            'Ó',
            'Ô',
            'Õ',
            'Ö',
            'ò',
            'ó',
            'ô',
            'õ',
            'ö',
            'Ù',
            'Ú',
            'Û',
            'Ü',
            'ù',
            'ú',
            'û',
            'ü',
            'Ç',
            'ç',
            'Ñ',
            'ñ'
        ];
        $semAcentos = [
            'A',
            'A',
            'A',
            'A',
            'A',
            'a',
            'a',
            'a',
            'a',
            'a',
            'E',
            'E',
            'E',
            'E',
            'e',
            'e',
            'e',
            'e',
            'I',
            'I',
            'I',
            'I',
            'i',
            'i',
            'i',
            'i',
            'O',
            'O',
            'O',
            'O',
            'O',
            'o',
            'o',
            'o',
            'o',
            'o',
            'U',
            'U',
            'U',
            'U',
            'u',
            'u',
            'u',
            'u',
            'C',
            'c',
            'N',
            'n'
        ];
        return str_replace($acentos, $semAcentos, $string);
    }

    /**
     * Busca código IBGE do município usando API do IBGE
     */
    private function buscarCodigoMunicipioIBGE($cidade, $uf)
    {
        try {
            $ufNormalizada = strtoupper(trim($uf));

            // API do IBGE para buscar código do município
            // Endpoint: https://servicodados.ibge.gov.br/api/v1/localidades/estados/{UF}/municipios
            $url = "https://servicodados.ibge.gov.br/api/v1/localidades/estados/{$ufNormalizada}/municipios";

            $this->log("🔍 Buscando código IBGE via API para: {$cidade}/{$uf}");

            $ch = curl_init();
            curl_setopt($ch, CURLOPT_URL, $url);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_TIMEOUT, 10);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);

            $response = @curl_exec($ch);
            $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $curlError = curl_error($ch);
            curl_close($ch);

            if ($curlError) {
                $this->log("⚠️ Erro cURL ao buscar código IBGE: {$curlError}");
            }

            if ($httpCode === 200 && !empty($response)) {
                $municipios = json_decode($response, true);

                if (is_array($municipios) && !empty($municipios)) {
                    $cidadeNormalizadaBusca = $this->normalizarNomeCidade($cidade);

                    foreach ($municipios as $municipio) {
                        $nomeMunicipio = strtoupper($municipio['nome'] ?? '');
                        $nomeMunicipioNormalizado = $this->normalizarNomeCidade($nomeMunicipio);

                        // Comparação exata primeiro
                        if ($nomeMunicipioNormalizado === $cidadeNormalizadaBusca) {
                            $codigo = (string) ($municipio['id'] ?? '');
                            if (!empty($codigo) && strlen($codigo) === 7) {
                                $this->log("✅ Código IBGE encontrado via API (exato): {$codigo} para {$cidade}/{$uf}");
                                return $codigo;
                            }
                        }
                    }

                    // Se não encontrou exato, tentar busca parcial
                    foreach ($municipios as $municipio) {
                        $nomeMunicipio = strtoupper($municipio['nome'] ?? '');
                        $nomeMunicipioNormalizado = $this->normalizarNomeCidade($nomeMunicipio);

                        if (
                            strpos($nomeMunicipioNormalizado, $cidadeNormalizadaBusca) !== false ||
                            strpos($cidadeNormalizadaBusca, $nomeMunicipioNormalizado) !== false
                        ) {
                            $codigo = (string) ($municipio['id'] ?? '');
                            if (!empty($codigo) && strlen($codigo) === 7) {
                                $this->log("✅ Código IBGE encontrado via API (similaridade): {$codigo} para {$cidade}/{$uf}");
                                return $codigo;
                            }
                        }
                    }
                }
            } else {
                $this->log("⚠️ API IBGE retornou código HTTP: {$httpCode}");
            }

            $this->log("⚠️ Código IBGE não encontrado via API para: {$cidade}/{$uf}");
            return null;

        } catch (\Exception $e) {
            $this->log("❌ Erro ao buscar código IBGE via API: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Cria estrutura de diretórios
     */
    private function criarEstruturaDiretorios($cnpj)
    {
        $cnpj = preg_replace('/\D/', '', $cnpj);
        $anoMes = date('Y_m');

        $diretorios = [
            __DIR__ . "/../../arquivos/{$cnpj}/nfe/xml/{$anoMes}/assinados",
            __DIR__ . "/../../arquivos/{$cnpj}/nfe/pdf/{$anoMes}/emitidos"
        ];

        foreach ($diretorios as $diretorio) {
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }
        }
    }

    /**
     * Salva XML real
     */
    private function salvarXMLReal($xml, $dados)
    {
        $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
        $chaveAcesso = $this->gerarChaveAcessoCompleta($dados);
        $anoMes = date('Y_m', strtotime($dados['data_emissao']));

        $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/nfe/xml/{$anoMes}/assinados";
        if (!is_dir($diretorio)) {
            mkdir($diretorio, 0755, true);
        }

        $nomeArquivo = "{$chaveAcesso}.xml";
        $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

        file_put_contents($caminhoCompleto, $xml);

        return "/pontti_nfe/arquivos/{$cnpj}/nfe/xml/{$anoMes}/assinados/{$nomeArquivo}";
    }

    /**
     * Consulta status da NF-e na SEFAZ
     */
    private function consultarStatusNFe($chaveAcesso, $empresa)
    {
        try {
            $this->log("🔍 Consultando status da NF-e: " . $chaveAcesso);

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($empresa);
            if (!$certificado) {
                return ['autorizada' => false, 'status' => 'Certificado não encontrado'];
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $empresa['nome'],
                'cnpj' => preg_replace('/\D/', '', $empresa['cnpj']),
                'ie' => preg_replace('/\D/', '', $empresa['inscricao_estadual']),
                'siglaUF' => $empresa['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Timeout de 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Timeout de conexão de 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura
                'soap_write_timeout' => 180, // Timeout de escrita
                'soap_force_curl' => true,
                'soap_force_ipv4' => true
            ];

            $configJson = json_encode($config);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // Consultar status da NF-e
            $response = $tools->sefazConsultaChave($chaveAcesso);

            // Padronizar resposta
            $st = new \NFePHP\NFe\Common\Standardize($response);
            $std = $st->toStd();

            $this->log("📋 Status da NF-e - cStat: " . ($std->cStat ?? 'N/A'));
            $this->log("📋 Status da NF-e - xMotivo: " . ($std->xMotivo ?? 'N/A'));

            // Verificar se está autorizada (cStat 100 = Autorizada)
            $autorizada = ($std->cStat == 100);
            $status = $std->xMotivo ?? 'Status desconhecido';

            // Verificar se já foi cancelada
            $jaCancelada = (
                strpos($status, 'Cancelamento') !== false ||
                strpos($status, 'cancelada') !== false ||
                strpos($status, 'homologado') !== false
            );

            return [
                'autorizada' => $autorizada,
                'ja_cancelada' => $jaCancelada,
                'status' => $status,
                'cStat' => $std->cStat ?? null,
                'xMotivo' => $std->xMotivo ?? null
            ];

        } catch (\Exception $e) {
            $this->log("❌ Erro ao consultar status da NF-e: " . $e->getMessage());
            return [
                'autorizada' => false,
                'status' => 'Erro na consulta: ' . $e->getMessage()
            ];
        }
    }

    /**
     * Traduz erros de cancelamento para mensagens mais claras
     */
    private function traduzirErroCancelamento($cStat, $motivo)
    {
        switch ($cStat) {
            case 580:
                return "❌ NF-e não pode ser cancelada: A NF-e não está autorizada na SEFAZ ou já foi cancelada anteriormente.";

            case 573:
                return "❌ NF-e não pode ser cancelada: A NF-e já foi cancelada anteriormente.";

            case 574:
                return "❌ NF-e não pode ser cancelada: A NF-e já foi inutilizada.";

            case 575:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento.";

            case 576:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de inutilização.";

            case 577:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de correção.";

            case 578:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de carta de correção.";

            case 579:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de carta de correção eletrônica.";

            case 580:
                return "❌ NF-e não pode ser cancelada: A NF-e não está autorizada na SEFAZ.";

            case 581:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 582:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 583:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 584:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 585:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 586:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 587:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 588:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 589:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 590:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 591:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 592:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 593:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 594:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 595:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 596:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 597:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 598:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            case 599:
                return "❌ NF-e não pode ser cancelada: A NF-e está em processamento de cancelamento por substituição.";

            default:
                return "❌ Cancelamento rejeitado pela SEFAZ: " . $motivo;
        }
    }

    /**
     * Cancela NFe
     */
    public function cancelarNFe($dados)
    {
        try {
            // Validar e normalizar chave de acesso (deve ter exatamente 44 dígitos)
            $chaveAcesso = preg_replace('/\D/', '', $dados['chave_acesso'] ?? '');

            if (strlen($chaveAcesso) != 44) {
                $this->log("❌ ERRO: Chave de acesso inválida! Esperado: 44 dígitos, Encontrado: " . strlen($chaveAcesso) . " dígitos");
                $this->log("❌ Chave recebida: '{$dados['chave_acesso']}' -> Normalizada: '{$chaveAcesso}'");
                throw new \Exception("Chave de acesso inválida. A chave deve ter exatamente 44 dígitos. Chave recebida: " . strlen($chaveAcesso) . " dígitos.");
            }

            $dados['chave_acesso'] = $chaveAcesso;
            $this->log("🚫 CANCELANDO NFe: " . $dados['chave_acesso']);

            // Verificar se a NF-e está autorizada antes de tentar cancelar
            $statusNFe = $this->consultarStatusNFe($dados['chave_acesso'], $dados['empresa']);

            if ($statusNFe['ja_cancelada']) {
                throw new \Exception("❌ NF-e já foi cancelada anteriormente. Status: " . $statusNFe['status']);
            }

            if (!$statusNFe['autorizada']) {
                throw new \Exception("❌ NF-e não está autorizada na SEFAZ. Status: " . $statusNFe['status']);
            }

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($dados['empresa']);
            if (!$certificado) {
                throw new \Exception("Certificado digital não encontrado");
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $dados['empresa']['nome'],
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj']),
                'ie' => preg_replace('/\D/', '', $dados['empresa']['inscricao_estadual']),
                'siglaUF' => $dados['empresa']['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Timeout de 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Timeout de conexão de 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura
                'soap_write_timeout' => 180, // Timeout de escrita
                'soap_force_curl' => true,
                'soap_force_ipv4' => true
            ];

            $configJson = json_encode($config);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // Gerar evento de cancelamento
            $evento = $tools->sefazCancela(
                $dados['chave_acesso'],
                $dados['justificativa'],
                $dados['protocolo_autorizacao'] ?? null
            );

            // Verificar se houve erro na geração do evento
            if (is_string($evento) && strpos($evento, 'Este XML não é válido') !== false) {
                $this->log("❌ Erro de validação XML detectado: " . $evento);
                throw new \Exception($evento);
            }

            // Capturar o XML do evento que foi enviado
            $xmlEventoEnviado = $tools->lastRequest;

            $this->log("📋 Evento de cancelamento gerado");
            $this->log("📋 XML do evento enviado: " . substr($xmlEventoEnviado, 0, 1000));

            // CORREÇÃO CRÍTICA: Validar e corrigir chNFe e ID do evento
            // Verificar chNFe (deve ter 44 dígitos)
            if (preg_match('/<chNFe>([^<]+)<\/chNFe>/', $xmlEventoEnviado, $matchesChNFe)) {
                $chNFe = preg_replace('/\D/', '', $matchesChNFe[1]);
                if (strlen($chNFe) != 44) {
                    $this->log("❌ ERRO CRÍTICO: chNFe tem tamanho incorreto! Esperado: 44, Encontrado: " . strlen($chNFe));
                    $this->log("❌ chNFe problemático: '{$matchesChNFe[1]}' -> Normalizado: '{$chNFe}'");
                    throw new \Exception("chNFe inválida no XML. Esperado: 44 dígitos, Encontrado: " . strlen($chNFe) . " dígitos. Valor: '{$chNFe}'");
                }
                // Se a chNFe no XML estiver diferente da normalizada, corrigir
                if ($chNFe !== $matchesChNFe[1]) {
                    $this->log("🔧 Corrigindo chNFe no XML: '{$matchesChNFe[1]}' -> '{$chNFe}'");
                    $xmlEventoEnviado = str_replace('<chNFe>' . $matchesChNFe[1] . '</chNFe>', '<chNFe>' . $chNFe . '</chNFe>', $xmlEventoEnviado);
                }
            }

            // CORREÇÃO CRÍTICA: Remover espaços extras do ID do evento
            $xmlEventoEnviado = preg_replace('/Id="([^"]*?)\s+"/', 'Id="$1"', $xmlEventoEnviado);

            // Verificar se há problema no ID do evento (deve ter 54 caracteres: ID + 2 dígitos tipo + 44 dígitos chave + 2 dígitos sequencial)
            if (preg_match('/Id="([^"]+)"/', $xmlEventoEnviado, $matches)) {
                $idEvento = $matches[1];
                $this->log("📋 ID do evento encontrado: '{$idEvento}' (tamanho: " . strlen($idEvento) . ")");

                if (strlen($idEvento) != 54) {
                    $this->log("⚠️ ATENÇÃO: ID do evento tem tamanho incorreto! Esperado: 54, Encontrado: " . strlen($idEvento));
                    $this->log("⚠️ ID problemático: '{$idEvento}'");

                    // Tentar corrigir o ID: ID + 21 (tipo cancelamento) + chave de acesso (44 dígitos) + 01 (sequencial)
                    // Usar a chave de acesso já validada e normalizada
                    $chaveParaId = $dados['chave_acesso'];
                    $idCorrigido = 'ID21' . $chaveParaId . '01';

                    if (strlen($idCorrigido) == 54) {
                        $this->log("🔧 Corrigindo ID do evento: '{$idEvento}' -> '{$idCorrigido}'");
                        $xmlEventoEnviado = str_replace('Id="' . $idEvento . '"', 'Id="' . $idCorrigido . '"', $xmlEventoEnviado);
                        $this->log("✅ ID do evento corrigido com sucesso");
                    } else {
                        // Tentar corrigir removendo espaços extras primeiro
                        $idCorrigido = trim($idEvento);
                        $idCorrigido = preg_replace('/\s+/', '', $idCorrigido);

                        if (strlen($idCorrigido) == 54) {
                            $this->log("🔧 Corrigindo ID do evento (removendo espaços): '{$idEvento}' -> '{$idCorrigido}'");
                            $xmlEventoEnviado = str_replace('Id="' . $idEvento . '"', 'Id="' . $idCorrigido . '"', $xmlEventoEnviado);
                            $this->log("✅ ID do evento corrigido com sucesso");
                        } else {
                            $this->log("❌ ID ainda incorreto após correção. Tamanho: " . strlen($idCorrigido));
                            $this->log("❌ ID problemático: '{$idCorrigido}'");
                            throw new \Exception("ID do evento inválido no XML. Esperado: 54 caracteres (ID + tipo + chave + sequencial), Encontrado: " . strlen($idCorrigido) . " caracteres. Valor: '{$idCorrigido}'");
                        }
                    }
                }
            }

            // Log do XML após correções
            $this->log("📋 XML do evento após correções: " . substr($xmlEventoEnviado, 0, 1000));

            // Padronizar resposta
            $st = new \NFePHP\NFe\Common\Standardize($evento);
            $std = $st->toStd();

            // Se o XML foi corrigido, usar o XML corrigido para validação
            if (isset($idCorrigido) && $idCorrigido != $idEvento) {
                $this->log("📋 Usando XML corrigido para validação");
                // Re-validar com o XML corrigido se necessário
            }

            $this->log("📋 Resposta do cancelamento - cStat: " . ($std->cStat ?? 'N/A'));
            $this->log("📋 Resposta do cancelamento - xMotivo: " . ($std->xMotivo ?? 'N/A'));
            $this->log("📋 Resposta completa: " . json_encode($std));

            // Verificar se foi processado com sucesso (cStat 128 = Lote de Evento Processado)
            if ($std->cStat == 128) {
                // Verificar se o evento específico foi processado com sucesso
                $eventoStat = $std->retEvento->infEvento->cStat ?? null;
                $eventoMotivo = $std->retEvento->infEvento->xMotivo ?? null;

                $this->log("📋 Evento específico - cStat: " . ($eventoStat ?? 'N/A'));
                $this->log("📋 Evento específico - xMotivo: " . ($eventoMotivo ?? 'N/A'));

                // Verificar se o evento foi autorizado (cStat 135 = Evento de Cancelamento Autorizado)
                if ($eventoStat == 135) {
                    // Tentar extrair protocolo de diferentes formas
                    $protocoloCancelamento = null;
                    $dataCancelamento = null;

                    // Forma 1: retEvento->infEvento->nProt
                    if (isset($std->retEvento->infEvento->nProt)) {
                        $protocoloCancelamento = $std->retEvento->infEvento->nProt;
                        $dataCancelamento = $std->retEvento->infEvento->dhRegEvento ?? null;
                    }

                    if ($protocoloCancelamento) {
                        $this->log("✅ NFe cancelada com sucesso! Protocolo: " . $protocoloCancelamento);

                        // Montar XML protocolado (procEventoNFe = evento + retEvento)
                        $xmlProtocolado = $this->montarXMLProtocoladoCancelamento($xmlEventoEnviado, $evento);

                        // Salvar XML do cancelamento
                        $xmlPath = $this->salvarXMLCancelamento($xmlProtocolado, $dados);

                        // Gerar PDF do cancelamento automaticamente
                        $pdfPath = null;
                        try {
                            $this->log("📄 Iniciando geração de PDF do cancelamento...");
                            $pdfPath = $this->gerarPDFCancelamento($xmlProtocolado, $dados);
                            if ($pdfPath) {
                                $this->log("✅ PDF do cancelamento gerado com sucesso: {$pdfPath}");
                            } else {
                                $this->log("⚠️ PDF do cancelamento não foi gerado (retornou null)");
                            }
                        } catch (\Throwable $pdfError) {
                            $this->log("❌ ERRO ao gerar PDF do cancelamento: " . $pdfError->getMessage());
                            $this->log("❌ Arquivo: " . $pdfError->getFile() . " - Linha: " . $pdfError->getLine());
                            // Continua mesmo se o PDF falhar
                        }

                        return [
                            'success' => true,
                            'chave_acesso' => $dados['chave_acesso'],
                            'protocolo_cancelamento' => $protocoloCancelamento,
                            'data_cancelamento' => $dataCancelamento,
                            'xml_path' => $xmlPath,
                            'pdf_path' => $pdfPath,
                            'message' => 'NFe cancelada com sucesso'
                        ];
                    } else {
                        $this->log("⚠️ Cancelamento autorizado mas protocolo não encontrado");
                        return [
                            'success' => true,
                            'chave_acesso' => $dados['chave_acesso'],
                            'protocolo_cancelamento' => 'AUTORIZADO_SEM_PROTOCOLO',
                            'data_cancelamento' => $std->retEvento->infEvento->dhRegEvento ?? date('Y-m-d H:i:s'),
                            'message' => 'Cancelamento autorizado pela SEFAZ'
                        ];
                    }
                } else {
                    // Evento rejeitado
                    $this->log("❌ Evento de cancelamento rejeitado - cStat: {$eventoStat}, Motivo: " . $eventoMotivo);

                    // Mensagens mais específicas para erros comuns
                    $mensagemErro = $this->traduzirErroCancelamento($eventoStat, $eventoMotivo);

                    return [
                        'success' => false,
                        'error' => $mensagemErro,
                        'cStat' => (string) $eventoStat,
                        'motivo' => $eventoMotivo
                    ];
                }
            } else {
                $motivo = $std->xMotivo ?? 'Motivo não informado';
                $cStat = $std->cStat ?? 'N/A';
                $this->log("❌ Erro ao processar lote de cancelamento - cStat: {$cStat}, Motivo: " . $motivo);
                return [
                    'success' => false,
                    'error' => 'Erro ao processar lote na SEFAZ: ' . $motivo,
                    'cStat' => (string) $cStat,
                    'motivo' => $motivo
                ];
            }
        } catch (\Exception $e) {
            $this->log("❌ Erro ao cancelar NFe: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Inutiliza numeração de NF-e
     */
    public function inutilizarNFe($dados)
    {
        try {
            $this->log("🚫 INUTILIZANDO NUMERAÇÃO NFe - Série: {$dados['serie']}, Números: {$dados['numero_inicial']} a {$dados['numero_final']}");

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($dados['empresa']);
            if (!$certificado) {
                throw new \Exception("Certificado digital não encontrado");
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $dados['empresa']['nome'],
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj']),
                'ie' => preg_replace('/\D/', '', $dados['empresa']['inscricao_estadual']),
                'siglaUF' => $dados['empresa']['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Timeout de 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Timeout de conexão de 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura
                'soap_write_timeout' => 180, // Timeout de escrita
                'soap_force_curl' => true,
                'soap_force_ipv4' => true
            ];

            $configJson = json_encode($config);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // Parâmetros para inutilização
            $serie = intval($dados['serie']);
            $numeroInicial = intval($dados['numero_inicial']);
            $numeroFinal = intval($dados['numero_final']);
            $justificativa = trim($dados['justificativa']);
            $ano = date('y'); // Ano atual com 2 dígitos (ex: 25 para 2025)
            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']); // Para logs

            // Validar justificativa antes de enviar
            if (strlen($justificativa) < 15) {
                throw new \Exception("Justificativa deve ter pelo menos 15 caracteres. Atual: " . strlen($justificativa) . " caracteres");
            }

            $this->log("📋 Parâmetros de inutilização:");
            $this->log("  - Empresa: {$dados['empresa']['nome']} (CNPJ: {$cnpj})");
            $this->log("  - Série: {$serie}");
            $this->log("  - Número inicial: {$numeroInicial}");
            $this->log("  - Número final: {$numeroFinal}");
            $this->log("  - Ano: {$ano}");
            $this->log("  - Justificativa: {$justificativa} (tamanho: " . strlen($justificativa) . " caracteres)");

            // Enviar inutilização para SEFAZ
            // ORDEM CORRETA: (serie, numero_inicial, numero_final, justificativa, ambiente, ano)
            $response = $tools->sefazInutiliza(
                $serie,           // 1º - Série (int)
                $numeroInicial,   // 2º - Número inicial (int)
                $numeroFinal,     // 3º - Número final (int)
                $justificativa,   // 4º - Justificativa (string)
                1,                // 5º - Ambiente (1 = Produção, 2 = Homologação)
                $ano              // 6º - Ano (2 dígitos) - opcional
            );

            $this->log("📋 Resposta da SEFAZ recebida");
            $this->log("📋 Resposta XML: " . substr($response, 0, 500) . "...");

            // Padronizar resposta
            $st = new \NFePHP\NFe\Common\Standardize($response);
            $std = $st->toStd();

            // Logar resposta completa para debug
            $this->log("📋 Resposta completa da SEFAZ: " . json_encode($std));

            // Para inutilização, os dados podem estar em infInut diretamente ou dentro de retInutNFe
            $infInut = $std->infInut ?? $std->retInutNFe->infInut ?? null;

            if (!$infInut) {
                $this->log("❌ Resposta não contém infInut");
                throw new \Exception("Resposta da SEFAZ em formato inesperado: " . json_encode($std));
            }

            $cStat = $infInut->cStat ?? null;
            $xMotivo = $infInut->xMotivo ?? 'Motivo não informado';

            $this->log("📋 Resposta da inutilização - cStat: " . ($cStat ?? 'N/A'));
            $this->log("📋 Resposta da inutilização - xMotivo: " . $xMotivo);

            // Verificar se foi processado com sucesso (cStat 102 = Inutilização de número homologado)
            if ($cStat == 102) {
                $protocolo = $infInut->nProt ?? null;
                $dataInutilizacao = $infInut->dhRecbto ?? date('Y-m-d H:i:s');

                $this->log("✅ Numeração inutilizada com sucesso! Protocolo: " . $protocolo);

                return [
                    'success' => true,
                    'protocolo_inutilizacao' => $protocolo,
                    'data_inutilizacao' => $dataInutilizacao,
                    'cStat' => '102',
                    'message' => 'Inutilização de número homologado'
                ];
            } elseif ($cStat == 563) {
                // Erro 563: Duplicidade - numeração já foi inutilizada antes
                $protocolo = $infInut->nProt ?? null;
                $this->log("⚠️ Numeração já foi inutilizada anteriormente. Protocolo: " . $protocolo);

                return [
                    'success' => false,
                    'cStat' => '563',
                    'protocolo_inutilizacao' => $protocolo,
                    'error' => 'Rejeição [563]: Já existe pedido de inutilização com a mesma faixa de inutilização',
                    'observacao' => 'Esta numeração já foi inutilizada anteriormente'
                ];
            } else {
                $this->log("❌ Erro na inutilização [" . $cStat . "]: " . $xMotivo);

                return [
                    'success' => false,
                    'cStat' => $cStat,
                    'error' => 'Rejeição [' . $cStat . ']: ' . $xMotivo
                ];
            }
        } catch (\Exception $e) {
            $this->log("❌ Erro ao inutilizar numeração: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Envia Carta de Correção Eletrônica (CC-e)
     */
    public function cartaCorrecao($dados)
    {
        try {
            // Validar e normalizar chave de acesso (deve ter exatamente 44 dígitos)
            $chaveAcesso = preg_replace('/\D/', '', $dados['chave_acesso'] ?? '');

            if (strlen($chaveAcesso) != 44) {
                $this->log("❌ ERRO: Chave de acesso inválida! Esperado: 44 dígitos, Encontrado: " . strlen($chaveAcesso) . " dígitos");
                $this->log("❌ Chave recebida: '{$dados['chave_acesso']}' -> Normalizada: '{$chaveAcesso}'");
                throw new \Exception("Chave de acesso inválida. A chave deve ter exatamente 44 dígitos. Chave recebida: " . strlen($chaveAcesso) . " dígitos.");
            }

            $dados['chave_acesso'] = $chaveAcesso;
            $this->log("📝 ENVIANDO CARTA DE CORREÇÃO - Chave: {$dados['chave_acesso']}");

            // Buscar certificado digital
            $certificado = $this->buscarCertificadoDigital($dados['empresa']);
            if (!$certificado) {
                throw new \Exception("Certificado digital não encontrado");
            }

            // Configuração para SEFAZ
            $config = [
                'atualizacao' => date('Y-m-d H:i:s'),
                'tpAmb' => 1, // Produção
                'razaosocial' => $dados['empresa']['nome'],
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj']),
                'ie' => preg_replace('/\D/', '', $dados['empresa']['inscricao_estadual']),
                'siglaUF' => $dados['empresa']['estado'],
                'schemes' => 'PL_009_V4',
                'versao' => '4.00',
                'timezone' => 'America/Recife',
                'soap_timeout' => 180, // Timeout de 180 segundos (3 minutos)
                'soap_connection_timeout' => 90, // Timeout de conexão de 90 segundos
                'soap_read_timeout' => 180, // Timeout de leitura
                'soap_write_timeout' => 180, // Timeout de escrita
                'soap_force_curl' => true,
                'soap_force_ipv4' => true
            ];

            $configJson = json_encode($config);

            // Criar instância do Tools
            $tools = new Tools($configJson, $certificado);
            $tools->model(55);

            // Número sequencial do evento (incrementa a cada CC-e para a mesma NFe)
            // Por padrão, usamos 1 (primeira correção)
            $numeroSequencial = $dados['numero_sequencial'] ?? 1;

            // Texto da correção
            $correcao = trim($dados['correcao']);

            // Validar tamanho da correção
            if (strlen($correcao) < 15) {
                throw new \Exception("Correção deve ter pelo menos 15 caracteres. Atual: " . strlen($correcao) . " caracteres");
            }

            if (strlen($correcao) > 1000) {
                throw new \Exception("Correção não pode ter mais de 1000 caracteres. Atual: " . strlen($correcao) . " caracteres");
            }

            $this->log("📋 Parâmetros da carta de correção:");
            $this->log("  - Chave de acesso: {$dados['chave_acesso']}");
            $this->log("  - Número sequencial: {$numeroSequencial}");
            $this->log("  - Correção: {$correcao}");
            $this->log("  - Tamanho da correção: " . strlen($correcao) . " caracteres");

            // Enviar carta de correção para SEFAZ
            $response = $tools->sefazCCe(
                $dados['chave_acesso'],
                $correcao,
                $numeroSequencial
            );

            // Capturar o XML do evento que foi enviado
            $xmlEventoEnviado = $tools->lastRequest;

            $this->log("📋 Resposta da SEFAZ recebida");

            // Padronizar resposta
            $st = new \NFePHP\NFe\Common\Standardize($response);
            $std = $st->toStd();

            // Logar resposta completa para debug
            $this->log("📋 Resposta completa da SEFAZ: " . json_encode($std));

            // Verificar se retEvento existe
            if (!isset($std->retEvento)) {
                $this->log("❌ Resposta não contém retEvento");
                throw new \Exception("Resposta da SEFAZ em formato inesperado: " . json_encode($std));
            }

            // Para CC-e, os dados estão dentro de retEvento->infEvento
            $infEvento = $std->retEvento->infEvento ?? null;

            if (!$infEvento) {
                throw new \Exception("Resposta da SEFAZ não contém infEvento: " . json_encode($std));
            }

            $cStat = $infEvento->cStat ?? null;
            $xMotivo = $infEvento->xMotivo ?? 'Motivo não informado';

            $this->log("📋 Resposta da CC-e - cStat: " . ($cStat ?? 'N/A'));
            $this->log("📋 Resposta da CC-e - xMotivo: " . $xMotivo);

            // Verificar se foi registrada com sucesso (cStat 135 = Evento registrado e vinculado a NF-e)
            if ($cStat == 135) {
                $protocolo = $infEvento->nProt ?? null;
                $dataRegistro = $infEvento->dhRegEvento ?? date('Y-m-d H:i:s');

                $this->log("✅ Carta de Correção registrada com sucesso! Protocolo: " . $protocolo);

                // Montar XML protocolado (procEventoNFe = evento + retEvento)
                $xmlProtocolado = $this->montarXMLProtocoladoCCe($xmlEventoEnviado, $response);

                // Salvar XML da CC-e
                $xmlPath = $this->salvarXMLCartaCorrecao($xmlProtocolado, $dados);

                // Gerar PDF da CC-e automaticamente
                $pdfPath = null;
                try {
                    $this->log("📄 Iniciando geração de PDF...");
                    $pdfPath = $this->gerarPDFCartaCorrecao($xmlProtocolado, $dados);
                    if ($pdfPath) {
                        $this->log("✅ PDF gerado com sucesso: {$pdfPath}");
                    } else {
                        $this->log("⚠️ PDF não foi gerado (retornou null)");
                    }
                } catch (\Throwable $pdfError) {
                    $this->log("❌ ERRO ao gerar PDF: " . $pdfError->getMessage());
                    $this->log("❌ Arquivo: " . $pdfError->getFile() . " - Linha: " . $pdfError->getLine());
                    $this->log("❌ Stack trace: " . $pdfError->getTraceAsString());
                    // Continua mesmo se o PDF falhar
                }

                $resultado = [
                    'success' => true,
                    'chave_acesso' => $dados['chave_acesso'],
                    'protocolo_cce' => $protocolo,
                    'numero_sequencial' => $numeroSequencial,
                    'data_registro' => $dataRegistro,
                    'cStat' => '135',
                    'xml_path' => $xmlPath,
                    'message' => 'Evento registrado e vinculado a NF-e'
                ];

                // Adicionar PDF path apenas se foi gerado
                if ($pdfPath) {
                    $resultado['pdf_path'] = $pdfPath;
                } else {
                    $resultado['observacao'] = 'XML salvo com sucesso. PDF não foi gerado (use o XML para visualizar)';
                }

                return $resultado;
            } else {
                $this->log("❌ Erro ao registrar CC-e [" . $cStat . "]: " . $xMotivo);

                return [
                    'success' => false,
                    'cStat' => $cStat,
                    'error' => 'Rejeição [' . $cStat . ']: ' . $xMotivo
                ];
            }
        } catch (\Exception $e) {
            $this->log("❌ Erro ao enviar carta de correção: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Monta XML protocolado para CC-e (procEventoNFe)
     */
    private function montarXMLProtocoladoCCe($xmlEvento, $xmlResposta)
    {
        try {
            $this->log("📋 Montando XML protocolado (procEventoNFe)");

            // Carregar o XML do evento enviado
            $domEvento = new \DOMDocument('1.0', 'UTF-8');
            $domEvento->loadXML($xmlEvento);
            $evento = $domEvento->getElementsByTagName('evento')->item(0);

            // Carregar o XML da resposta da SEFAZ
            $domResposta = new \DOMDocument('1.0', 'UTF-8');
            $domResposta->loadXML($xmlResposta);
            $retEvento = $domResposta->getElementsByTagName('retEvento')->item(0);

            if (!$evento) {
                throw new \Exception("XML do evento não encontrado");
            }

            if (!$retEvento) {
                throw new \Exception("XML de retorno (retEvento) não encontrado na resposta da SEFAZ");
            }

            // Criar o XML protocolado (procEventoNFe)
            $domProc = new \DOMDocument('1.0', 'UTF-8');
            $domProc->formatOutput = false;
            $domProc->preserveWhiteSpace = false;

            $procEventoNFe = $domProc->createElementNS('http://www.portalfiscal.inf.br/nfe', 'procEventoNFe');
            $procEventoNFe->setAttribute('versao', '1.00');
            $domProc->appendChild($procEventoNFe);

            // Importar e adicionar o evento
            $eventoImportado = $domProc->importNode($evento, true);
            $procEventoNFe->appendChild($eventoImportado);

            // Importar e adicionar o retEvento
            $retEventoImportado = $domProc->importNode($retEvento, true);
            $procEventoNFe->appendChild($retEventoImportado);

            $xmlProtocolado = $domProc->saveXML();

            $this->log("✅ XML protocolado montado com sucesso");

            return $xmlProtocolado;
        } catch (\Exception $e) {
            $this->log("❌ Erro ao montar XML protocolado: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Salva XML da Carta de Correção
     */
    private function salvarXMLCartaCorrecao($xmlEvento, $dados)
    {
        try {
            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
            $chaveAcesso = $dados['chave_acesso'];
            $ano = date('Y');
            $mes = date('m');
            $numeroSeq = $dados['numero_sequencial'] ?? 1;

            // Criar estrutura de diretórios: arquivos/{cnpj}/cce/xml/{ano}_{mes}
            $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/cce/xml/{$ano}_{$mes}";
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }

            $nomeArquivo = "CCe_{$chaveAcesso}_v{$numeroSeq}.xml";
            $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

            // Salvar XML
            file_put_contents($caminhoCompleto, $xmlEvento);

            $this->log("✅ XML da CC-e salvo em: " . $caminhoCompleto);

            return "/pontti_nfe/arquivos/{$cnpj}/cce/xml/{$ano}_{$mes}/{$nomeArquivo}";
        } catch (\Exception $e) {
            $this->log("❌ Erro ao salvar XML da CC-e: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Gera PDF da Carta de Correção
     */
    private function gerarPDFCartaCorrecao($xmlEvento, $dados)
    {
        try {
            // Aumentar limite de tempo e memória para geração de PDF
            set_time_limit(180);
            ini_set('memory_limit', '512M');

            $this->log("📄 Gerando PDF da Carta de Correção");

            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
            $chaveAcesso = $dados['chave_acesso'];
            $ano = date('Y');
            $mes = date('m');
            $numeroSeq = $dados['numero_sequencial'] ?? 1;

            // Criar estrutura de diretórios: arquivos/{cnpj}/cce/pdf/{ano}_{mes}
            $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/cce/pdf/{$ano}_{$mes}";
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }

            $nomeArquivo = "CCe_{$chaveAcesso}_v{$numeroSeq}.pdf";
            $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

            // Preparar dados do emitente para o PDF (DEVE SER ARRAY, NÃO OBJETO!)
            $dadosEmitente = [
                'razao' => $dados['empresa']['nome'] ?? 'Empresa',
                'logradouro' => $dados['empresa']['endereco'] ?? 'Rua',
                'numero' => $dados['empresa']['numero'] ?? 'S/N',
                'complemento' => $dados['empresa']['complemento'] ?? '',
                'bairro' => $dados['empresa']['bairro'] ?? 'Centro',
                'CEP' => preg_replace('/\D/', '', $dados['empresa']['cep'] ?? '00000000'),
                'municipio' => $dados['empresa']['cidade'] ?? 'Cidade',
                'UF' => $dados['empresa']['estado'] ?? 'PE',
                'telefone' => $dados['empresa']['telefone'] ?? '(00) 0000-0000',
                'email' => $dados['empresa']['email'] ?? 'contato@empresa.com',
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj'] ?? '00000000000000'),
                'IE' => preg_replace('/\D/', '', $dados['empresa']['inscricao_estadual'] ?? '000000000')
            ];

            $this->log("📄 Instanciando Daevento...");
            $this->log("📄 Dados do emitente: " . json_encode($dadosEmitente));
            $this->log("📄 Tamanho do XML: " . strlen($xmlEvento) . " bytes");

            $daevento = new \NFePHP\DA\NFe\Daevento($xmlEvento, $dadosEmitente);
            $this->log("✅ Daevento instanciado com sucesso");

            $this->log("📄 Renderizando PDF...");
            $pdf = $daevento->render();
            $this->log("✅ PDF renderizado: " . strlen($pdf) . " bytes");

            $this->log("📄 Salvando PDF em: " . $caminhoCompleto);
            file_put_contents($caminhoCompleto, $pdf);
            $this->log("✅ PDF salvo com sucesso");

            return "/pontti_nfe/arquivos/{$cnpj}/cce/pdf/{$ano}_{$mes}/{$nomeArquivo}";

        } catch (\Throwable $e) {
            $this->log("❌ ERRO ao gerar PDF da CC-e: " . $e->getMessage());
            $this->log("❌ Tipo: " . get_class($e));
            $this->log("❌ Arquivo: " . $e->getFile() . " - Linha: " . $e->getLine());
            $this->log("❌ Stack trace: " . $e->getTraceAsString());

            // Retorna null mas não impede o sucesso da CC-e (XML já foi salvo)
            throw $e; // Re-lança o erro para ser capturado no método principal
        }
    }

    /**
     * Monta XML protocolado para Cancelamento (procEventoNFe)
     */
    private function montarXMLProtocoladoCancelamento($xmlEvento, $xmlResposta)
    {
        try {
            $this->log("📋 Montando XML protocolado de cancelamento (procEventoNFe)");

            // Carregar o XML do evento enviado
            $domEvento = new \DOMDocument('1.0', 'UTF-8');
            $domEvento->loadXML($xmlEvento);
            $evento = $domEvento->getElementsByTagName('evento')->item(0);

            // Carregar o XML da resposta da SEFAZ
            $domResposta = new \DOMDocument('1.0', 'UTF-8');
            $domResposta->loadXML($xmlResposta);
            $retEvento = $domResposta->getElementsByTagName('retEvento')->item(0);

            if (!$evento) {
                throw new \Exception("XML do evento de cancelamento não encontrado");
            }

            if (!$retEvento) {
                throw new \Exception("XML de retorno (retEvento) não encontrado na resposta da SEFAZ");
            }

            // Criar o XML protocolado (procEventoNFe)
            $domProc = new \DOMDocument('1.0', 'UTF-8');
            $domProc->formatOutput = false;
            $domProc->preserveWhiteSpace = false;

            $procEventoNFe = $domProc->createElementNS('http://www.portalfiscal.inf.br/nfe', 'procEventoNFe');
            $procEventoNFe->setAttribute('versao', '1.00');
            $domProc->appendChild($procEventoNFe);

            // Importar e adicionar o evento
            $eventoImportado = $domProc->importNode($evento, true);
            $procEventoNFe->appendChild($eventoImportado);

            // Importar e adicionar o retEvento
            $retEventoImportado = $domProc->importNode($retEvento, true);
            $procEventoNFe->appendChild($retEventoImportado);

            $xmlProtocolado = $domProc->saveXML();

            $this->log("✅ XML protocolado de cancelamento montado com sucesso");

            return $xmlProtocolado;
        } catch (\Exception $e) {
            $this->log("❌ Erro ao montar XML protocolado de cancelamento: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Salva XML do Cancelamento
     */
    private function salvarXMLCancelamento($xmlEvento, $dados)
    {
        try {
            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
            $chaveAcesso = $dados['chave_acesso'];
            $ano = date('Y');
            $mes = date('m');

            // Criar estrutura de diretórios: arquivos/{cnpj}/cancelamento/xml/{ano}_{mes}
            $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/cancelamento/xml/{$ano}_{$mes}";
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }

            $nomeArquivo = "Cancel_{$chaveAcesso}.xml";
            $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

            // Salvar XML
            file_put_contents($caminhoCompleto, $xmlEvento);

            $this->log("✅ XML do cancelamento salvo em: " . $caminhoCompleto);

            return "/pontti_nfe/arquivos/{$cnpj}/cancelamento/xml/{$ano}_{$mes}/{$nomeArquivo}";
        } catch (\Exception $e) {
            $this->log("❌ Erro ao salvar XML do cancelamento: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Gera PDF do Cancelamento
     */
    private function gerarPDFCancelamento($xmlEvento, $dados)
    {
        try {
            // Aumentar limite de tempo e memória para geração de PDF
            set_time_limit(180);
            ini_set('memory_limit', '512M');

            $this->log("📄 Gerando PDF do Cancelamento");

            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);
            $chaveAcesso = $dados['chave_acesso'];
            $ano = date('Y');
            $mes = date('m');

            // Criar estrutura de diretórios: arquivos/{cnpj}/cancelamento/pdf/{ano}_{mes}
            $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/cancelamento/pdf/{$ano}_{$mes}";
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }

            $nomeArquivo = "Cancel_{$chaveAcesso}.pdf";
            $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

            // Preparar dados do emitente para o PDF (DEVE SER ARRAY, NÃO OBJETO!)
            $dadosEmitente = [
                'razao' => $dados['empresa']['nome'] ?? 'Empresa',
                'logradouro' => $dados['empresa']['endereco'] ?? 'Rua',
                'numero' => $dados['empresa']['numero'] ?? 'S/N',
                'complemento' => $dados['empresa']['complemento'] ?? '',
                'bairro' => $dados['empresa']['bairro'] ?? 'Centro',
                'CEP' => preg_replace('/\D/', '', $dados['empresa']['cep'] ?? '00000000'),
                'municipio' => $dados['empresa']['cidade'] ?? 'Cidade',
                'UF' => $dados['empresa']['estado'] ?? 'PE',
                'telefone' => $dados['empresa']['telefone'] ?? '(00) 0000-0000',
                'email' => $dados['empresa']['email'] ?? 'contato@empresa.com',
                'cnpj' => preg_replace('/\D/', '', $dados['empresa']['cnpj'] ?? '00000000000000'),
                'IE' => preg_replace('/\D/', '', $dados['empresa']['inscricao_estadual'] ?? '000000000')
            ];

            $this->log("📄 Instanciando Daevento para cancelamento...");
            $this->log("📄 Dados do emitente: " . json_encode($dadosEmitente));
            $this->log("📄 Tamanho do XML: " . strlen($xmlEvento) . " bytes");

            $daevento = new \NFePHP\DA\NFe\Daevento($xmlEvento, $dadosEmitente);
            $this->log("✅ Daevento instanciado com sucesso");

            $this->log("📄 Renderizando PDF...");
            $pdf = $daevento->render();
            $this->log("✅ PDF renderizado: " . strlen($pdf) . " bytes");

            $this->log("📄 Salvando PDF em: " . $caminhoCompleto);
            file_put_contents($caminhoCompleto, $pdf);
            $this->log("✅ PDF do cancelamento salvo com sucesso");

            return "/pontti_nfe/arquivos/{$cnpj}/cancelamento/pdf/{$ano}_{$mes}/{$nomeArquivo}";

        } catch (\Throwable $e) {
            $this->log("❌ ERRO ao gerar PDF do cancelamento: " . $e->getMessage());
            $this->log("❌ Tipo: " . get_class($e));
            $this->log("❌ Arquivo: " . $e->getFile() . " - Linha: " . $e->getLine());
            $this->log("❌ Stack trace: " . $e->getTraceAsString());

            // Retorna null mas não impede o sucesso do cancelamento (XML já foi salvo)
            throw $e; // Re-lança o erro para ser capturado no método principal
        }
    }


    /**
     * Adiciona tags IBS/CBS ao XML gerado pelo NFePHP (DEPRECATED - não usar mais)
     * IBSCBS deve ser a ÚLTIMA tag dentro de <imposto>, após COFINSST e ICMSUFDest
     */
    private function adicionarIBSCBSAoXML(string $xml, array $dadosIBSCBS): string
    {
        try {
            // Para cada item com dados de IBS/CBS, adicionar as tags ANTES de </imposto>
            // Mas DEPOIS de todas as outras tags (COFINSST, ICMSUFDest, etc)
            foreach ($dadosIBSCBS as $nItem => $dados) {
                // Encontrar a tag </imposto> correspondente ao item
                // O padrão é: <det nItem="X">...<imposto>...COFINS...COFINSST?...ICMSUFDest?...</imposto>...</det>
                // Precisamos inserir IBSCBS ANTES de </imposto>, mas DEPOIS de todas as outras tags
                $pattern = '/(<det\s+nItem="' . $nItem . '"[^>]*>.*?<imposto>.*?)(<\/imposto>)/s';

                $tagsIBSCBS = '<IBSCBS>';
                $tagsIBSCBS .= '<CST>' . ($dados['CST'] ?? '000') . '</CST>';
                $tagsIBSCBS .= '<cClassTrib>' . ($dados['cClassTrib'] ?? '000001') . '</cClassTrib>';
                $tagsIBSCBS .= '<gIBSCBS>';
                $tagsIBSCBS .= '<vBC>' . number_format($dados['vBC'], 2, '.', '') . '</vBC>';
                $tagsIBSCBS .= '<gIBSUF>';
                $tagsIBSCBS .= '<pIBSUF>' . number_format($dados['pIBSUF'], 4, '.', '') . '</pIBSUF>';
                $tagsIBSCBS .= '<vIBSUF>' . number_format($dados['vIBSUF'], 2, '.', '') . '</vIBSUF>';
                $tagsIBSCBS .= '</gIBSUF>';
                $tagsIBSCBS .= '<gIBSMun>';
                $tagsIBSCBS .= '<pIBSMun>' . number_format($dados['pIBSMun'], 4, '.', '') . '</pIBSMun>';
                $tagsIBSCBS .= '<vIBSMun>' . number_format($dados['vIBSMun'], 2, '.', '') . '</vIBSMun>';
                $tagsIBSCBS .= '</gIBSMun>';
                $tagsIBSCBS .= '<vIBS>' . number_format($dados['vIBS'], 2, '.', '') . '</vIBS>';
                $tagsIBSCBS .= '<gCBS>';
                $tagsIBSCBS .= '<pCBS>' . number_format($dados['pCBS'], 4, '.', '') . '</pCBS>';
                $tagsIBSCBS .= '<vCBS>' . number_format($dados['vCBS'], 2, '.', '') . '</vCBS>';
                $tagsIBSCBS .= '</gCBS>';
                $tagsIBSCBS .= '</gIBSCBS>';
                $tagsIBSCBS .= '</IBSCBS>';

                // Inserir IBSCBS ANTES de </imposto>, mas garantir que está após COFINSST e ICMSUFDest
                // Se houver COFINSST ou ICMSUFDest, inserir após eles; caso contrário, inserir após COFINS
                $patternComCOFINSST = '/(<det\s+nItem="' . $nItem . '"[^>]*>.*?<imposto>.*?<\/COFINSST>)(<\/imposto>)/s';
                $patternComICMSUFDest = '/(<det\s+nItem="' . $nItem . '"[^>]*>.*?<imposto>.*?<\/ICMSUFDest>)(<\/imposto>)/s';
                $patternAposCOFINS = '/(<det\s+nItem="' . $nItem . '"[^>]*>.*?<imposto>.*?<\/COFINS>)(<\/imposto>)/s';

                // Tentar inserir após COFINSST primeiro (se existir)
                if (preg_match($patternComCOFINSST, $xml)) {
                    $replacement = '$1' . $tagsIBSCBS . '$2';
                    $xml = preg_replace($patternComCOFINSST, $replacement, $xml);
                    error_log("✅ IBSCBS inserido após COFINSST para item {$nItem}");
                }
                // Se não houver COFINSST, tentar após ICMSUFDest
                elseif (preg_match($patternComICMSUFDest, $xml)) {
                    $replacement = '$1' . $tagsIBSCBS . '$2';
                    $xml = preg_replace($patternComICMSUFDest, $replacement, $xml);
                    error_log("✅ IBSCBS inserido após ICMSUFDest para item {$nItem}");
                }
                // Se não houver nenhum dos dois, inserir após COFINS (padrão)
                else {
                    $replacement = '$1' . $tagsIBSCBS . '$2';
                    $xml = preg_replace($patternAposCOFINS, $replacement, $xml);
                    error_log("✅ IBSCBS inserido após COFINS para item {$nItem}");
                }
            }

            return $xml;
        } catch (\Exception $e) {
            $this->log("❌ Erro ao adicionar tags IBS/CBS ao XML: " . $e->getMessage());
            // Retornar XML original em caso de erro
            return $xml;
        }
    }

    /**
     * Gera PDF real
     */
    public function gerarPDFReal($dados, $xml, $protocolo = null, $chaveAcessoReal = null)
    {
        try {
            // CORREÇÃO: Normalizar estrutura dos dados para aceitar JSON com objeto 'nfe'
            if (isset($dados['nfe'])) {
                $dados['numero'] = $dados['nfe']['numero'] ?? $dados['numero'] ?? '1';
                $dados['serie'] = $dados['nfe']['serie'] ?? $dados['serie'] ?? '001';
                $dados['data_emissao'] = $dados['nfe']['data_emissao'] ?? $dados['data_emissao'] ?? date('Y-m-d H:i:s');
            }

            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);

            // CORREÇÃO CRÍTICA: Usar chave extraída do XML (da SEFAZ) ao invés de gerar
            // A chave gerada pode não corresponder à chave real do XML assinado
            if (empty($chaveAcessoReal)) {
                $chaveAcessoReal = $this->extrairChaveAcessoDoXML($xml);
                if (empty($chaveAcessoReal)) {
                    // Fallback: usar chave gerada se não conseguir extrair
                    $chaveAcessoReal = $this->gerarChaveAcessoCompleta($dados);
                    $this->log("⚠️ Não foi possível extrair chave do XML, usando chave gerada");
                } else {
                    $this->log("✅ Usando chave extraída do XML: " . $chaveAcessoReal);
                }
            } else {
                $this->log("✅ Usando chave fornecida: " . $chaveAcessoReal);
            }

            $anoMes = date('Y_m', strtotime($dados['data_emissao']));

            $diretorio = __DIR__ . "/../../arquivos/{$cnpj}/nfe/pdf/{$anoMes}/emitidos";
            if (!is_dir($diretorio)) {
                mkdir($diretorio, 0755, true);
            }

            $nomeArquivo = "NFe_{$chaveAcessoReal}.pdf";
            $caminhoCompleto = $diretorio . "/" . $nomeArquivo;

            // Gerar DANFE usando NFePHP - PASSANDO O PROTOCOLO
            $this->gerarDANFEComNFePHP($xml, $caminhoCompleto, $dados, $protocolo);

            return "/pontti_nfe/arquivos/{$cnpj}/nfe/pdf/{$anoMes}/emitidos/{$nomeArquivo}";
        } catch (\Exception $e) {
            error_log("Erro ao gerar PDF: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Gera DANFE usando NFePHP
     */
    private function gerarDANFEComNFePHP($xml, $caminhoCompleto, $dados, $protocolo = null)
    {
        try {
            $this->log("📄 Gerando DANFE com protocolo: " . ($protocolo ?? 'sem protocolo'));

            // Se temos o protocolo, precisamos adicionar ao XML
            if ($protocolo) {
                $this->log("✅ Protocolo disponível: " . $protocolo);

                // Criar XML protocolado (XML + protocolo)
                // O NFePHP espera o XML completo com a tag protNFe anexada
                $xmlProtocolado = $this->adicionarProtocoloAoXML($xml, $protocolo, $dados);

                if ($xmlProtocolado) {
                    $xml = $xmlProtocolado;
                    $this->log("✅ XML protocolado gerado com sucesso");
                }
            } else {
                $this->log("⚠️ DANFE será gerado SEM protocolo - nota sem valor fiscal");
            }

            // Gerar DANFE
            $danfe = new \NFePHP\DA\NFe\Danfe($xml);
            $danfe->debugMode(false);
            $danfe->creditsIntegratorFooter('Systhema Tecnologia');

            // CORREÇÃO: Adicionar logo do cliente se existir
            // NFeService está em Systhema/src/Integrations/NFe/src/Services/
            // Logo está em Systhema/storage/uploads/logos/[cnpj].jpg
            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj']);

            // Calcular caminho relativo: 4 níveis acima até Systhema/
            // __DIR__ = Systhema/src/Integrations/NFe/src/Services
            // subindo 5 níveis chegamos em Systhema/
            $basePath = dirname(__DIR__, 5); // Systhema/
            // fallback: se por algum motivo não existir, tentar um nível acima do atual base
            if (!is_dir($basePath . '/storage')) {
                $try = dirname(__DIR__, 4); // Systhema/src
                if (is_dir($try . '/../storage')) {
                    $basePath = realpath($try . '/..');
                }
            }
            $logosDir = $basePath . "/storage/uploads/logos";

            $logoPath = null;

            // 1) Se vier caminho explícito em empresa.logo, usar (PRIORIDADE MÁXIMA)
            if (!empty($dados['empresa']['logo'])) {
                $candidate = trim($dados['empresa']['logo']);

                // Normalizar caminho: pode vir como /storage/uploads/logos/... ou storage/uploads/logos/...
                if (strpos($candidate, '/storage/') === 0) {
                    // Caminho absoluto a partir da raiz do projeto (formato: /storage/uploads/logos/...)
                    $candidate = $basePath . $candidate;
                } elseif (strpos($candidate, 'storage/') === 0) {
                    // Caminho relativo sem barra inicial (formato: storage/uploads/logos/...)
                    $candidate = $basePath . '/' . $candidate;
                } elseif (file_exists($candidate)) {
                    // Já é caminho absoluto válido
                    // Usar como está
                } elseif (!str_starts_with($candidate, '/') && !file_exists($candidate)) {
                    // Se não começar com / e não existir, tentar como relativo ao basePath
                    $candidate = $basePath . '/' . ltrim($candidate, '/');
                }

                // Tentar caminho realpath para normalizar
                $candidateReal = realpath($candidate);
                if ($candidateReal !== false) {
                    $candidate = $candidateReal;
                }

                // Verificar se o arquivo existe
                if (file_exists($candidate)) {
                    $logoPath = $candidate;
                    $this->log("✅ Logo encontrado em empresas.logo: {$logoPath}");
                } else {
                    $this->log("⚠️ Logo especificado em empresas.logo não encontrado. Tentou: {$candidate}");
                    // Tentar variações do caminho
                    $variacoes = [
                        $basePath . '/storage/uploads/logos/' . basename($candidate),
                        $basePath . '/storage/uploads/logos/' . $cnpj . '.' . pathinfo($candidate, PATHINFO_EXTENSION),
                        dirname($candidate) . '/' . basename($candidate)
                    ];
                    foreach ($variacoes as $variacao) {
                        if (file_exists($variacao)) {
                            $logoPath = $variacao;
                            $this->log("✅ Logo encontrado em variação: {$logoPath}");
                            break;
                        }
                    }
                }
            }

            // 2) Tentar pelos padrões conhecidos no diretório de logos
            if (!$logoPath) {
                $rawCnpj = $dados['empresa']['cnpj'] ?? '';
                $candidates = [];
                foreach (['jpg', 'png', 'jpeg', 'JPG', 'PNG', 'JPEG'] as $ext) {
                    $candidates[] = $logosDir . "/{$cnpj}.{$ext}";
                    if (!empty($rawCnpj)) {
                        $candidates[] = $logosDir . "/{$rawCnpj}.{$ext}";
                    }
                }
                foreach ($candidates as $cand) {
                    if (file_exists($cand)) {
                        $logoPath = $cand;
                        break;
                    }
                }
            }

            if ($logoPath && file_exists($logoPath)) {
                $this->log("✅ Logo encontrado: {$logoPath}");
                try {
                    if (method_exists($danfe, 'logoParameters')) {
                        // Versões do NFePHP que aceitam caminho e parâmetros opcionais
                        // (largura/altura em mm geralmente são opcionais)
                        $danfe->logoParameters($logoPath, null, null);
                    } elseif (method_exists($danfe, 'setLogo')) {
                        // Algumas versões utilizam setLogo
                        $danfe->setLogo($logoPath);
                    } else {
                        $this->log("⚠️ Método de logo não disponível na classe Danfe. Prosseguindo sem logo.");
                    }
                    $this->log("✅ Logo configurado no DANFE");
                } catch (\Exception $e) {
                    $this->log("⚠️ Erro ao adicionar logo: " . $e->getMessage());
                }
            } else {
                $this->log("⚠️ Logo não encontrado. Procurado em: {$logosDir} com CNPJ {$cnpj} e variantes.");
            }

            // Gerar PDF usando método correto
            $pdf = $danfe->render();
            file_put_contents($caminhoCompleto, $pdf);

            $this->log("✅ DANFE gerado com sucesso em: " . $caminhoCompleto);
        } catch (\Exception $e) {
            $this->log("❌ Erro ao gerar DANFE: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Adiciona o protocolo ao XML da NFe
     */
    private function adicionarProtocoloAoXML($xml, $protocolo, $dados)
    {
        try {
            // Obter a chave de acesso do XML
            $chaveAcesso = $this->extrairChaveAcessoDoXML($xml);

            // Adicionar o protocolo manualmente ao XML
            return $this->adicionarProtocoloManualmente($xml, $protocolo, $chaveAcesso);
        } catch (\Exception $e) {
            $this->log("❌ Erro ao adicionar protocolo ao XML: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Adiciona o protocolo manualmente ao XML
     */
    private function adicionarProtocoloManualmente($xml, $protocolo, $chaveAcesso)
    {
        try {
            // Criar estrutura do protocolo
            $protocoloXML = '<protNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">';
            $protocoloXML .= '<infProt>';
            $protocoloXML .= '<tpAmb>1</tpAmb>';
            $protocoloXML .= '<verAplic>1.0</verAplic>';
            $protocoloXML .= '<chNFe>' . $chaveAcesso . '</chNFe>';
            $protocoloXML .= '<dhRecbto>' . date('c') . '</dhRecbto>';
            $protocoloXML .= '<nProt>' . $protocolo . '</nProt>';
            $protocoloXML .= '<digVal>' . base64_encode('HASH_DIGEST') . '</digVal>';
            $protocoloXML .= '<cStat>100</cStat>';
            $protocoloXML .= '<xMotivo>Autorizado o uso da NF-e</xMotivo>';
            $protocoloXML .= '</infProt>';
            $protocoloXML .= '</protNFe>';

            // Criar estrutura nfeProc (XML + protocolo)
            $xmlProtocolado = '<?xml version="1.0" encoding="UTF-8"?>';
            $xmlProtocolado .= '<nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">';

            // Remover declaração XML do XML original
            $xmlLimpo = preg_replace('/<\?xml.*?\?>/', '', $xml);
            $xmlLimpo = trim($xmlLimpo);

            $xmlProtocolado .= $xmlLimpo;
            $xmlProtocolado .= $protocoloXML;
            $xmlProtocolado .= '</nfeProc>';

            $this->log("✅ Protocolo adicionado manualmente ao XML");
            return $xmlProtocolado;
        } catch (\Exception $e) {
            $this->log("❌ Erro ao adicionar protocolo manualmente: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Cria XML protocolado manualmente usando a resposta da SEFAZ
     */
    private function criarXMLProtocoladoManualmente($xml, $protNFe)
    {
        try {
            // Criar estrutura do protocolo a partir da resposta da SEFAZ
            $protocoloXML = '<protNFe versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">';
            $protocoloXML .= '<infProt>';
            $protocoloXML .= '<tpAmb>' . ($protNFe->infProt->tpAmb ?? '1') . '</tpAmb>';
            $protocoloXML .= '<verAplic>' . ($protNFe->infProt->verAplic ?? '1.0') . '</verAplic>';
            $protocoloXML .= '<chNFe>' . $protNFe->infProt->chNFe . '</chNFe>';
            $protocoloXML .= '<dhRecbto>' . $protNFe->infProt->dhRecbto . '</dhRecbto>';
            $protocoloXML .= '<nProt>' . $protNFe->infProt->nProt . '</nProt>';
            $protocoloXML .= '<digVal>' . $protNFe->infProt->digVal . '</digVal>';
            $protocoloXML .= '<cStat>' . $protNFe->infProt->cStat . '</cStat>';
            $protocoloXML .= '<xMotivo>' . $protNFe->infProt->xMotivo . '</xMotivo>';
            $protocoloXML .= '</infProt>';
            $protocoloXML .= '</protNFe>';

            // Criar estrutura nfeProc (XML + protocolo)
            $xmlProtocolado = '<?xml version="1.0" encoding="UTF-8"?>';
            $xmlProtocolado .= '<nfeProc versao="4.00" xmlns="http://www.portalfiscal.inf.br/nfe">';

            // Remover declaração XML do XML original
            $xmlLimpo = preg_replace('/<\?xml.*?\?>/', '', $xml);
            $xmlLimpo = trim($xmlLimpo);

            $xmlProtocolado .= $xmlLimpo;
            $xmlProtocolado .= $protocoloXML;
            $xmlProtocolado .= '</nfeProc>';

            error_log("✅ XML protocolado criado manualmente com dados da SEFAZ");
            return $xmlProtocolado;
        } catch (\Exception $e) {
            error_log("❌ Erro ao criar XML protocolado: " . $e->getMessage());
            return null;
        }
    }

    /**
     * Gera XML simples e válido
     */
    private function gerarXMLSimples($nfe)
    {
        $xml = '<?xml version="1.0" encoding="UTF-8"?>';
        $xml .= '<NFe xmlns="http://www.portalfiscal.inf.br/nfe">';
        $xml .= '<infNFe Id="' . $nfe['infNFe']['Id'] . '" versao="' . $nfe['infNFe']['versao'] . '">';

        // IDE
        $xml .= '<ide>';
        foreach ($nfe['infNFe']['ide'] as $tag => $valor) {
            $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
        }
        $xml .= '</ide>';

        // EMIT
        $xml .= '<emit>';
        $xml .= '<CNPJ>' . $nfe['infNFe']['emit']['CNPJ'] . '</CNPJ>';
        $xml .= '<xNome>' . $nfe['infNFe']['emit']['xNome'] . '</xNome>';
        $xml .= '<xFant>' . $nfe['infNFe']['emit']['xFant'] . '</xFant>';
        $xml .= '<enderEmit>';
        foreach ($nfe['infNFe']['emit']['enderEmit'] as $tag => $valor) {
            $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
        }
        $xml .= '</enderEmit>';
        $xml .= '<IE>' . $nfe['infNFe']['emit']['IE'] . '</IE>';
        $xml .= '<CRT>' . $nfe['infNFe']['emit']['CRT'] . '</CRT>';
        $xml .= '</emit>';

        // DEST
        $xml .= '<dest>';
        $xml .= '<CNPJ>' . $nfe['infNFe']['dest']['CNPJ'] . '</CNPJ>';
        $xml .= '<xNome>' . $nfe['infNFe']['dest']['xNome'] . '</xNome>';
        $xml .= '<enderDest>';
        foreach ($nfe['infNFe']['dest']['enderDest'] as $tag => $valor) {
            $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
        }
        $xml .= '</enderDest>';
        $xml .= '<indIEDest>' . $nfe['infNFe']['dest']['indIEDest'] . '</indIEDest>';
        $xml .= '<IE>' . $nfe['infNFe']['dest']['IE'] . '</IE>';
        $xml .= '</dest>';

        // DET (itens)
        foreach ($nfe['infNFe']['det'] as $item) {
            $xml .= '<det nItem="' . $item['nItem'] . '">';
            $xml .= '<prod>';
            foreach ($item['prod'] as $tag => $valor) {
                $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
            }
            $xml .= '</prod>';
            $xml .= '<imposto>';
            $xml .= '<vTotTrib>' . $item['imposto']['vTotTrib'] . '</vTotTrib>';
            $xml .= '<ICMS>';
            $xml .= '<ICMS00>';
            foreach ($item['imposto']['ICMS']['ICMS00'] as $tag => $valor) {
                $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
            }
            $xml .= '</ICMS00>';
            $xml .= '</ICMS>';
            $xml .= '<IPI>';
            $xml .= '<cEnq>' . $item['imposto']['IPI']['cEnq'] . '</cEnq>';
            $xml .= '<IPITrib>';
            foreach ($item['imposto']['IPI']['IPITrib'] as $tag => $valor) {
                $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
            }
            $xml .= '</IPITrib>';
            $xml .= '</IPI>';
            $xml .= '<PIS>';
            $xml .= '<PISAliq>';
            foreach ($item['imposto']['PIS']['PISAliq'] as $tag => $valor) {
                $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
            }
            $xml .= '</PISAliq>';
            $xml .= '</PIS>';
            $xml .= '<COFINS>';
            $xml .= '<COFINSAliq>';
            foreach ($item['imposto']['COFINS']['COFINSAliq'] as $tag => $valor) {
                $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
            }
            $xml .= '</COFINSAliq>';
            $xml .= '</COFINS>';

            // IBS/CBS - Nova tributação
            if (isset($item['imposto']['IBSCBS']) && !empty($item['imposto']['IBSCBS'])) {
                $ibsCbs = $item['imposto']['IBSCBS'];
                $xml .= '<IBSCBS>';
                if (isset($ibsCbs['CST'])) {
                    $xml .= '<CST>' . $ibsCbs['CST'] . '</CST>';
                }
                if (isset($ibsCbs['cClassTrib'])) {
                    $xml .= '<cClassTrib>' . $ibsCbs['cClassTrib'] . '</cClassTrib>';
                }
                if (isset($ibsCbs['gIBSCBS'])) {
                    $xml .= '<gIBSCBS>';
                    if (isset($ibsCbs['gIBSCBS']['vBC'])) {
                        $xml .= '<vBC>' . number_format($ibsCbs['gIBSCBS']['vBC'], 2, '.', '') . '</vBC>';
                    }
                    if (isset($ibsCbs['gIBSCBS']['gIBSUF'])) {
                        $xml .= '<gIBSUF>';
                        if (isset($ibsCbs['gIBSCBS']['gIBSUF']['pIBSUF'])) {
                            $xml .= '<pIBSUF>' . number_format($ibsCbs['gIBSCBS']['gIBSUF']['pIBSUF'], 4, '.', '') . '</pIBSUF>';
                        }
                        if (isset($ibsCbs['gIBSCBS']['gIBSUF']['vIBSUF'])) {
                            $xml .= '<vIBSUF>' . number_format($ibsCbs['gIBSCBS']['gIBSUF']['vIBSUF'], 2, '.', '') . '</vIBSUF>';
                        }
                        $xml .= '</gIBSUF>';
                    }
                    if (isset($ibsCbs['gIBSCBS']['gIBSMun'])) {
                        $xml .= '<gIBSMun>';
                        if (isset($ibsCbs['gIBSCBS']['gIBSMun']['pIBSMun'])) {
                            $xml .= '<pIBSMun>' . number_format($ibsCbs['gIBSCBS']['gIBSMun']['pIBSMun'], 4, '.', '') . '</pIBSMun>';
                        }
                        if (isset($ibsCbs['gIBSCBS']['gIBSMun']['vIBSMun'])) {
                            $xml .= '<vIBSMun>' . number_format($ibsCbs['gIBSCBS']['gIBSMun']['vIBSMun'], 2, '.', '') . '</vIBSMun>';
                        }
                        $xml .= '</gIBSMun>';
                    }
                    if (isset($ibsCbs['gIBSCBS']['vIBS'])) {
                        $xml .= '<vIBS>' . number_format($ibsCbs['gIBSCBS']['vIBS'], 2, '.', '') . '</vIBS>';
                    }
                    if (isset($ibsCbs['gIBSCBS']['gCBS'])) {
                        $xml .= '<gCBS>';
                        if (isset($ibsCbs['gIBSCBS']['gCBS']['pCBS'])) {
                            $xml .= '<pCBS>' . number_format($ibsCbs['gIBSCBS']['gCBS']['pCBS'], 4, '.', '') . '</pCBS>';
                        }
                        if (isset($ibsCbs['gIBSCBS']['gCBS']['vCBS'])) {
                            $xml .= '<vCBS>' . number_format($ibsCbs['gIBSCBS']['gCBS']['vCBS'], 2, '.', '') . '</vCBS>';
                        }
                        $xml .= '</gCBS>';
                    }
                    $xml .= '</gIBSCBS>';
                }
                $xml .= '</IBSCBS>';
            }

            $xml .= '</imposto>';
            $xml .= '</det>';
        }

        // TOTAL
        $xml .= '<total>';
        $xml .= '<ICMSTot>';
        foreach ($nfe['infNFe']['total']['ICMSTot'] as $tag => $valor) {
            $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
        }
        $xml .= '</ICMSTot>';
        $xml .= '</total>';

        // TRANSP
        $xml .= '<transp>';
        $xml .= '<modFrete>' . $nfe['infNFe']['transp']['modFrete'] . '</modFrete>';
        $xml .= '</transp>';

        // PAG
        $xml .= '<pag>';
        $xml .= '<detPag>';
        foreach ($nfe['infNFe']['pag']['detPag'] as $tag => $valor) {
            $xml .= '<' . $tag . '>' . $valor . '</' . $tag . '>';
        }
        $xml .= '</detPag>';
        $xml .= '</pag>';

        $xml .= '</infNFe>';
        $xml .= '</NFe>';

        return $xml;
    }

    /**
     * Gera DANFE de preview antes da emissão
     */
    public function previewDanfe($dados)
    {
        try {
            $this->log("🔍 GERANDO PREVIEW DANFE");

            // Normalizar estrutura dos dados
            if (isset($dados['nfe'])) {
                $dados['numero'] = $dados['nfe']['numero'] ?? $dados['numero'] ?? '1';
                $dados['serie'] = $dados['nfe']['serie'] ?? $dados['serie'] ?? '001';
                $dados['data_emissao'] = $dados['nfe']['data_emissao'] ?? $dados['data_emissao'] ?? date('Y-m-d H:i:s');
            }

            if (empty($dados['empresa']['cnpj'])) {
                throw new \Exception('CNPJ da empresa ausente para gerar preview');
            }

            // Gerar XML da NF-e (sem enviar para SEFAZ)
            $dados['preview'] = true;
            $xml = $this->gerarXMLReal($dados);

            // Definir caminho do PDF de preview
            $cnpj = preg_replace('/\D/', '', $dados['empresa']['cnpj'] ?? '');
            $anoMes = date('Y_m');
            $basePath = __DIR__ . "/../../arquivos/{$cnpj}/nfe/pdf/{$anoMes}/preview";
            if (!is_dir($basePath)) {
                if (!@mkdir($basePath, 0775, true) && !is_dir($basePath)) {
                    throw new \Exception('Falha ao criar diretório de preview: ' . $basePath);
                }
            }
            $nomeArquivo = 'NFe_PREVIEW_' . time() . '.pdf';
            $caminhoCompleto = $basePath . '/' . $nomeArquivo;

            // Gerar PDF do DANFE (sem protocolo)
            $this->gerarDANFEComNFePHP($xml, $caminhoCompleto, $dados, null);

            if ($caminhoCompleto && file_exists($caminhoCompleto)) {
                $this->log("✅ Preview DANFE gerado: " . $caminhoCompleto);

                return [
                    'success' => true,
                    'pdf_path' => $caminhoCompleto,
                    'pdf_url' => \App\Helpers\UrlHelper::url('/vendas/visualizar-pdf-preview?path=' . rawurlencode($caminhoCompleto)),
                    'message' => 'Preview DANFE gerado com sucesso'
                ];
            } else {
                $this->log("❌ Erro ao gerar preview DANFE - arquivo não encontrado após render: " . $caminhoCompleto);
                return [
                    'success' => false,
                    'error' => 'Erro ao gerar preview DANFE: arquivo não foi criado'
                ];
            }

        } catch (\Exception $e) {
            $this->log("❌ Erro no preview DANFE: " . $e->getMessage());
            return [
                'success' => false,
                'error' => 'Erro ao gerar preview: ' . $e->getMessage()
            ];
        }
    }
}
