<?php

declare(strict_types=1);

namespace App\Services\NFSe;

use Exception;

/**
 * Service principal para emissão de NFS-e via Portal Nacional
 */
class NFSeNacionalService
{
    private NFSeNacionalClient $client;
    private bool $homologacao;

    /**
     * @param string|null $clientId Client ID OAuth2 (opcional - pode usar apenas certificado)
     * @param string|null $clientSecret Client Secret OAuth2 (opcional - pode usar apenas certificado)
     * @param bool $homologacao
     * @param array|null $empresa Dados da empresa com certificado (opcional)
     */
    public function __construct(?string $clientId = null, ?string $clientSecret = null, bool $homologacao = false, ?array $empresa = null)
    {
        $this->homologacao = $homologacao;

        // Se tem credenciais OAuth2, usa autenticação OAuth2
        if (!empty($clientId) && !empty($clientSecret)) {
            $auth = new NFSeNacionalAuthService($clientId, $clientSecret, $homologacao);
            $this->client = new NFSeNacionalClient($auth, $homologacao, null, null);
        }
        // Se não tem OAuth2 mas tem certificado, usa apenas certificado
        elseif (!empty($empresa) && !empty($empresa['certificado_path']) && !empty($empresa['senha_certificado'])) {
            $certPath = $empresa['certificado_path'];
            $senhaCert = $empresa['senha_certificado'];

            // Normalizar caminho do certificado (mesmo padrão usado no sistema)
            if (strpos($certPath, ROOT_PATH) === 0) {
                // Já tem ROOT_PATH, usa como está
                $certPath = $certPath;
            } elseif (strpos($certPath, '/') === 0) {
                // Caminho absoluto começando com /
                $certPath = ROOT_PATH . $certPath;
            } else {
                // Caminho relativo, adiciona ROOT_PATH
                $certPath = ROOT_PATH . '/' . ltrim($certPath, '/');
            }

            // Verificar se arquivo existe
            if (!file_exists($certPath)) {
                throw new Exception("Certificado digital não encontrado em: $certPath. Verifique o caminho configurado na empresa.");
            }

            $this->client = new NFSeNacionalClient(null, $homologacao, $certPath, $senhaCert);
        } else {
            throw new Exception('É necessário informar: (1) Client ID e Secret OAuth2 OU (2) Certificado digital da empresa (certificado_path e senha_certificado).');
        }
    }

    /**
     * Emite NFS-e a partir de dados da venda
     */
    public function emitirNfse(array $dadosVenda, array $empresa, array $configuracao): array
    {
        try {
            // 1. Montar dados do DPS
            $dadosDps = $this->montarPayloadNfse($dadosVenda, $empresa, $configuracao);

            // 2. Validar dados antes de gerar XML
            $this->validarDados($dadosDps);

            // 3. Gerar DPS XML conforme padrão do Portal Nacional
            $dpsXml = $this->gerarDpsXml($dadosDps, $empresa, $configuracao);

            // 4. Compactar em GZip e codificar em base64 (formato esperado pela API)
            $dpsGzip = gzencode($dpsXml, 9);
            $dpsB64 = base64_encode($dpsGzip);

            // 5. Montar payload final conforme Swagger: {"dpsXmlGZipB64": "..."}
            $payload = [
                'dpsXmlGZipB64' => $dpsB64
            ];

            error_log("✅ DPS XML gerado e compactado. Tamanho original: " . strlen($dpsXml) . " bytes, compactado: " . strlen($dpsGzip) . " bytes");

            // 6. Enviar para API
            $resultado = $this->client->emitirNfse($payload);

            // 7. Processar resposta da API
            // Resposta conforme Swagger: {"nfseXmlGZipB64": "...", "chaveAcesso": "...", "idDps": "...", ...}
            $chaveAcesso = $resultado['chaveAcesso'] ?? $resultado['chave_acesso'] ?? null;
            $idDps = $resultado['idDps'] ?? $resultado['id_dps'] ?? null;

            // Extrair número da NFS-e da chave de acesso (se disponível)
            $numeroNfse = null;
            if ($chaveAcesso && strlen($chaveAcesso) >= 15) {
                // A chave de acesso tem 50 caracteres, o número da NFS-e está em uma posição específica
                // Por enquanto, usar o idDps ou tentar extrair da chave
                $numeroNfse = $idDps ?? substr($chaveAcesso, -8);
            }

            return [
                'success' => true,
                'numero_nfse' => $numeroNfse,
                'codigo_verificacao' => $chaveAcesso, // Usar chave de acesso como código de verificação
                'data_emissao' => $resultado['dataHoraProcessamento'] ?? date('Y-m-d H:i:s'),
                'xml_path' => null, // Será processado depois se tiver nfseXmlGZipB64
                'pdf_path' => null, // PDF não vem na resposta direta
                'qr_code' => null,
                'chave_acesso' => $chaveAcesso,
                'id_dps' => $idDps,
                'nfse_xml' => $resultado['nfse_xml'] ?? null, // XML descompactado pelo Client
                'nfseXmlGZipB64' => $resultado['nfseXmlGZipB64'] ?? null, // XML compactado original
            ];
        } catch (Exception $e) {
            error_log('Erro ao emitir NFS-e via Portal Nacional: ' . $e->getMessage());
            return [
                'success' => false,
                'erro' => $e->getMessage(),
            ];
        }
    }

    /**
     * Consulta NFS-e por número
     */
    public function consultarNfse(string $numeroNfse, ?string $codigoVerificacao = null): array
    {
        try {
            return $this->client->consultarNfse($numeroNfse, $codigoVerificacao);
        } catch (Exception $e) {
            error_log('Erro ao consultar NFS-e: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Cancela NFS-e
     */
    public function cancelarNfse(string $numeroNfse, string $codigoVerificacao, string $motivo): array
    {
        try {
            return $this->client->cancelarNfse($numeroNfse, $codigoVerificacao, $motivo);
        } catch (Exception $e) {
            error_log('Erro ao cancelar NFS-e: ' . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Monta payload JSON no formato do Portal Nacional
     */
    private function montarPayloadNfse(array $venda, array $empresa, array $config): array
    {
        $cnpj = preg_replace('/\D/', '', $empresa['cnpj'] ?? $empresa['document'] ?? '');
        $inscricaoMunicipal = $this->sanitizarNumero($empresa['inscricao_municipal'] ?? '');

        if (strlen($cnpj) !== 14) {
            throw new Exception('CNPJ do prestador inválido.');
        }

        if (empty($inscricaoMunicipal)) {
            throw new Exception('Inscrição municipal do prestador obrigatória.');
        }

        // Montar prestador
        $prestador = [
            'cnpj' => $cnpj,
            'inscricao_municipal' => $inscricaoMunicipal,
            'razao_social' => $empresa['razao_social'] ?? $empresa['name'] ?? '',
        ];

        // Adicionar endereço do prestador se disponível
        $endereco = $empresa['endereco'] ?? '';
        $bairro = $empresa['bairro'] ?? $empresa['neighborhood'] ?? '';
        $cep = $empresa['cep'] ?? $empresa['zipcode'] ?? '';
        $cidade = $empresa['cidade'] ?? '';
        $uf = $empresa['uf'] ?? $empresa['state'] ?? 'PE';

        if (!empty($endereco) || !empty($cep)) {
            $prestador['endereco'] = [
                'logradouro' => $endereco,
                'numero' => $empresa['numero'] ?? $empresa['address_number'] ?? 'S/N',
                'complemento' => $empresa['complemento'] ?? $empresa['address_complement'] ?? null,
                'bairro' => $bairro,
                'codigo_municipio' => $empresa['city_code'] ?? '2611606', // Recife
                'uf' => strtoupper($uf),
                'cep' => !empty($cep) ? preg_replace('/\D/', '', $cep) : null,
            ];
        }

        // Montar tomador
        $tomador = $this->montarDadosTomador($venda);

        // Montar serviço
        $valorServicos = (float) ($venda['total'] ?? 0);
        $aliquotaIss = (float) ($config['aliquota_iss'] ?? 0);
        $issRetido = ($config['iss_retido'] ?? '2') === '1';
        $valorIss = $issRetido ? 0.0 : round($valorServicos * ($aliquotaIss / 100), 2);

        $servico = [
            'codigo_servico' => trim((string) ($config['codigo_servico'] ?? '')),
            'codigo_tributacao' => trim((string) ($config['codigo_tributacao'] ?? $config['codigo_servico'] ?? '')),
            'discriminacao' => trim((string) ($config['descricao_servico'] ?? '')),
            'valor_servicos' => $valorServicos,
            'aliquota' => $aliquotaIss,
            'valor_iss' => $valorIss,
            'iss_retido' => $issRetido,
            'natureza_operacao' => (int) ($config['natureza_operacao'] ?? 1),
            'optante_simples' => ($config['optante_simples'] ?? '2') === '1',
        ];

        if (!empty($config['regime_tributacao'])) {
            $servico['regime_tributacao'] = $config['regime_tributacao'];
        }

        // Montar payload completo
        $payload = [
            'prestador' => $prestador,
            'tomador' => $tomador,
            'servico' => $servico,
            'data_emissao' => date('Y-m-d\TH:i:s'),
        ];

        return $payload;
    }

    /**
     * Monta dados do tomador/cliente
     */
    private function montarDadosTomador(array $venda): array
    {
        $cliente = $venda['cliente'] ?? [];

        // Tentar obter CPF/CNPJ de diferentes campos
        $documento = $cliente['cpf_cnpj'] ?? $cliente['document'] ?? $cliente['cpf'] ?? $cliente['cnpj'] ?? '';
        $documentoLimpo = preg_replace('/\D/', '', $documento);

        $cpf = null;
        $cnpj = null;

        if (strlen($documentoLimpo) === 11) {
            $cpf = $documentoLimpo;
        } elseif (strlen($documentoLimpo) === 14) {
            $cnpj = $documentoLimpo;
        }

        // Nome/Razão Social
        $razaoSocial = $cliente['razao_social']
            ?? $cliente['trade_name']
            ?? $cliente['name']
            ?? $venda['customer_name']
            ?? 'Consumidor não identificado';

        $tomador = [
            'razao_social' => $razaoSocial,
        ];

        // Identificação (CPF ou CNPJ)
        if ($cnpj && strlen($cnpj) === 14) {
            $tomador['identificacao'] = [
                'cnpj' => $cnpj,
            ];
        } elseif ($cpf && strlen($cpf) === 11) {
            $tomador['identificacao'] = [
                'cpf' => $cpf,
            ];
        }

        // Endereço do tomador
        $endereco = $cliente['endereco'] ?? [];
        $logradouro = $endereco['logradouro'] ?? $cliente['address'] ?? $venda['customer_address'] ?? '';
        $cep = $endereco['cep'] ?? $cliente['zip_code'] ?? $cliente['cep'] ?? $venda['customer_zip'] ?? '';

        if (!empty($logradouro) || !empty($cep)) {
            $tomador['endereco'] = [
                'logradouro' => $logradouro,
                'numero' => $endereco['numero'] ?? $cliente['address_number'] ?? $venda['customer_number'] ?? 'S/N',
                'complemento' => $endereco['complemento'] ?? $cliente['address_complement'] ?? null,
                'bairro' => $endereco['bairro'] ?? $cliente['district'] ?? $cliente['neighborhood'] ?? $venda['customer_district'] ?? '',
                'codigo_municipio' => $endereco['codigo_municipio'] ?? $cliente['city_code'] ?? '2611606', // Recife
                'uf' => strtoupper($endereco['uf'] ?? $cliente['state'] ?? $venda['customer_state'] ?? 'PE'),
                'cep' => !empty($cep) ? preg_replace('/\D/', '', $cep) : null,
            ];
        }

        // Contato (se disponível)
        $contato = $cliente['contato'] ?? [];
        $telefone = $contato['telefone'] ?? $cliente['phone'] ?? $cliente['mobile'] ?? $venda['customer_phone'] ?? '';
        $email = $contato['email'] ?? $cliente['email'] ?? $venda['customer_email'] ?? '';

        if (!empty($telefone) || !empty($email)) {
            $tomador['contato'] = [];
            if (!empty($telefone)) {
                $tomador['contato']['telefone'] = preg_replace('/\D/', '', $telefone);
            }
            if (!empty($email)) {
                $tomador['contato']['email'] = $email;
            }
        }

        return $tomador;
    }

    /**
     * Valida dados antes de enviar
     */
    private function validarDados(array $payload): void
    {
        // Validar prestador
        if (empty($payload['prestador']['cnpj']) || strlen($payload['prestador']['cnpj']) !== 14) {
            throw new Exception('CNPJ do prestador inválido.');
        }

        if (empty($payload['prestador']['inscricao_municipal'])) {
            throw new Exception('Inscrição municipal do prestador obrigatória.');
        }

        if (empty($payload['prestador']['razao_social'])) {
            throw new Exception('Razão social do prestador obrigatória.');
        }

        // Validar serviço
        if (empty($payload['servico']['codigo_servico'])) {
            throw new Exception('Código do serviço obrigatório.');
        }

        if (empty($payload['servico']['valor_servicos']) || $payload['servico']['valor_servicos'] <= 0) {
            throw new Exception('Valor dos serviços deve ser maior que zero.');
        }

        if (empty($payload['servico']['discriminacao'])) {
            throw new Exception('Descrição do serviço obrigatória.');
        }
    }

    /**
     * Remove caracteres não numéricos
     */
    private function sanitizarNumero(string $valor): string
    {
        return preg_replace('/\D/', '', $valor) ?? '';
    }

    /**
     * Gera o XML do DPS (Documento de Prestação de Serviço) conforme padrão do Portal Nacional
     * Baseado no XML fornecido pelo usuário e na estrutura do Portal Nacional
     */
    private function gerarDpsXml(array $dados, array $empresa, array $config): string
    {
        // Namespace conforme XML fornecido pelo usuário
        $xmlns = 'http://www.sped.fazenda.gov.br/nfse';

        $dom = new \DOMDocument('1.0', 'UTF-8');
        $dom->formatOutput = false; // Compacto para economizar espaço

        // Elemento raiz DPS
        $dps = $dom->createElementNS($xmlns, 'DPS');
        $dps->setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns', $xmlns);
        $dps->setAttribute('versao', '1.00');
        $dom->appendChild($dps);

        // infDPS
        $infDps = $dom->createElement('infDPS');
        $idDps = 'DPS' . ($dados['prestador']['cnpj'] ?? '') . date('YmdHis') . rand(1000, 9999);
        $infDps->setAttribute('Id', $idDps);
        $dps->appendChild($infDps);

        // tpAmb: 1-Produção, 2-Homologação
        $tpAmb = $this->homologacao ? '2' : '1';
        $this->appendTextNode($dom, $infDps, 'tpAmb', $tpAmb);

        // dhEmi: Data e hora de emissão
        $dhEmi = $dados['data_emissao'] ?? date('Y-m-d\TH:i:sP');
        $this->appendTextNode($dom, $infDps, 'dhEmi', $dhEmi);

        // verAplic: Versão do aplicativo
        $this->appendTextNode($dom, $infDps, 'verAplic', 'Systhema ERP 1.0');

        // serie: Série do DPS (geralmente 900 para NFS-e)
        $serie = $config['serie'] ?? '900';
        $this->appendTextNode($dom, $infDps, 'serie', $serie);

        // nDPS: Número do DPS (sequencial)
        $nDps = $dados['numero_dps'] ?? date('Ymd') . rand(100, 999);
        $this->appendTextNode($dom, $infDps, 'nDPS', $nDps);

        // dCompet: Data de competência (data de prestação do serviço)
        $dCompet = date('Y-m-d');
        $this->appendTextNode($dom, $infDps, 'dCompet', $dCompet);

        // tpEmit: Tipo de emitente (1-Prestador)
        $this->appendTextNode($dom, $infDps, 'tpEmit', '1');

        // cLocEmi: Código do município de emissão (IBGE)
        $cLocEmi = $empresa['city_code'] ?? '2611606'; // Recife
        $this->appendTextNode($dom, $infDps, 'cLocEmi', $cLocEmi);

        // prest: Prestador
        $prest = $dom->createElement('prest');
        $infDps->appendChild($prest);

        $cnpj = $dados['prestador']['cnpj'] ?? '';
        $this->appendTextNode($dom, $prest, 'CNPJ', $cnpj);

        $im = $dados['prestador']['inscricao_municipal'] ?? '';
        $this->appendTextNode($dom, $prest, 'IM', $im);

        // Contato do prestador
        if (!empty($empresa['phone'] ?? $empresa['telefone'] ?? '')) {
            $fone = preg_replace('/\D/', '', $empresa['phone'] ?? $empresa['telefone'] ?? '');
            if (!empty($fone)) {
                $this->appendTextNode($dom, $prest, 'fone', $fone);
            }
        }

        if (!empty($empresa['email'] ?? '')) {
            $this->appendTextNode($dom, $prest, 'email', $empresa['email']);
        }

        // regTrib: Regime tributário
        $regTrib = $dom->createElement('regTrib');
        $prest->appendChild($regTrib);

        $optanteSimples = ($dados['servico']['optante_simples'] ?? false) ? '1' : '2';
        $this->appendTextNode($dom, $regTrib, 'opSimpNac', $optanteSimples);
        $this->appendTextNode($dom, $regTrib, 'regApTribSN', '1');
        $this->appendTextNode($dom, $regTrib, 'regEspTrib', '0');

        // toma: Tomador
        $toma = $dom->createElement('toma');
        $infDps->appendChild($toma);

        $tomador = $dados['tomador'] ?? [];

        // Identificação do tomador
        if (isset($tomador['identificacao']['cnpj'])) {
            $this->appendTextNode($dom, $toma, 'CNPJ', $tomador['identificacao']['cnpj']);
        } elseif (isset($tomador['identificacao']['cpf'])) {
            $this->appendTextNode($dom, $toma, 'CPF', $tomador['identificacao']['cpf']);
        }

        // Nome do tomador
        if (!empty($tomador['razao_social'])) {
            $this->appendTextNode($dom, $toma, 'xNome', $tomador['razao_social']);
        }

        // Endereço do tomador (se disponível)
        if (!empty($tomador['endereco'])) {
            $end = $dom->createElement('end');
            $toma->appendChild($end);

            $endNac = $dom->createElement('endNac');
            $end->appendChild($endNac);

            $endereco = $tomador['endereco'];
            if (!empty($endereco['codigo_municipio'])) {
                $this->appendTextNode($dom, $endNac, 'cMun', $endereco['codigo_municipio']);
            }
            if (!empty($endereco['cep'])) {
                $this->appendTextNode($dom, $endNac, 'CEP', preg_replace('/\D/', '', $endereco['cep']));
            }
            if (!empty($endereco['logradouro'])) {
                $this->appendTextNode($dom, $endNac, 'xLgr', $endereco['logradouro']);
            }
            if (!empty($endereco['numero'])) {
                $this->appendTextNode($dom, $endNac, 'nro', $endereco['numero']);
            }
            if (!empty($endereco['bairro'])) {
                $this->appendTextNode($dom, $endNac, 'xBairro', $endereco['bairro']);
            }
        }

        // serv: Serviço
        $serv = $dom->createElement('serv');
        $infDps->appendChild($serv);

        // locPrest: Local de prestação
        $locPrest = $dom->createElement('locPrest');
        $serv->appendChild($locPrest);

        $cLocPrestacao = $cLocEmi; // Mesmo município de emissão
        $this->appendTextNode($dom, $locPrest, 'cLocPrestacao', $cLocPrestacao);

        // cServ: Código do serviço
        $cServ = $dom->createElement('cServ');
        $serv->appendChild($cServ);

        $servico = $dados['servico'] ?? [];
        $this->appendTextNode($dom, $cServ, 'cTribNac', $servico['codigo_servico'] ?? '');
        $this->appendTextNode($dom, $cServ, 'cTribMun', $servico['codigo_tributacao'] ?? $servico['codigo_servico'] ?? '');
        $this->appendTextNode($dom, $cServ, 'xDescServ', $servico['discriminacao'] ?? '');

        // valores: Valores
        $valores = $dom->createElement('valores');
        $infDps->appendChild($valores);

        $vServPrest = $dom->createElement('vServPrest');
        $valores->appendChild($vServPrest);

        $vServ = number_format($servico['valor_servicos'] ?? 0, 2, '.', '');
        $this->appendTextNode($dom, $vServPrest, 'vServ', $vServ);

        // trib: Tributação
        $trib = $dom->createElement('trib');
        $valores->appendChild($trib);

        $tribMun = $dom->createElement('tribMun');
        $trib->appendChild($tribMun);

        $issRetido = ($servico['iss_retido'] ?? false) ? '1' : '2';
        $this->appendTextNode($dom, $tribMun, 'tribISSQN', '1');
        $this->appendTextNode($dom, $tribMun, 'tpRetISSQN', $issRetido);

        $pAliq = number_format($servico['aliquota'] ?? 0, 2, '.', '');
        $this->appendTextNode($dom, $tribMun, 'pAliq', $pAliq);

        $totTrib = $dom->createElement('totTrib');
        $trib->appendChild($totTrib);

        // Calcular percentual total de tributação (simplificado)
        $pTotTribSN = '13.03'; // Valor padrão, pode ser calculado
        $this->appendTextNode($dom, $totTrib, 'pTotTribSN', $pTotTribSN);

        return $dom->saveXML();
    }

    /**
     * Helper para criar nós de texto no DOM
     */
    private function appendTextNode(\DOMDocument $dom, \DOMElement $parent, string $name, ?string $value): void
    {
        if ($value !== null && $value !== '') {
            $node = $dom->createElement($name);
            $node->appendChild($dom->createTextNode($value));
            $parent->appendChild($node);
        }
    }
}
