<?php

declare(strict_types=1);

namespace App\Services;

use NFePHP\NFe\Make;
use NFePHP\NFe\Tools;
use NFePHP\Common\Certificate;
use NFePHP\NFe\Common\Standardize;
use Exception;

/**
 * Serviço para Emissão de NF-e
 * Utiliza a biblioteca sped-nfe
 */
class NFeService
{
    private Tools $tools;
    private array $config;

    public function __construct()
    {
        $this->loadConfig();
        $this->initializeTools();
    }

    /**
     * Carrega configurações da NFe
     */
    private function loadConfig(): void
    {
        $this->config = [
            'atualizacao' => date('Y-m-d H:i:s'),
            'tpAmb' => $_ENV['NFE_AMBIENTE'] ?? 2, // 1-Produção, 2-Homologação
            'razaosocial' => $_ENV['NFE_RAZAO_SOCIAL'] ?? '',
            'siglaUF' => $_ENV['NFE_UF'] ?? 'SP',
            'cnpj' => $_ENV['NFE_CNPJ'] ?? '',
            'schemes' => 'PL_009_V4',
            'versao' => '4.00',
            'tokenIBPT' => '',
            'CSC' => $_ENV['NFE_CSC'] ?? '',
            'CSCid' => $_ENV['NFE_CSC_ID'] ?? '000001'
        ];
    }

    /**
     * Inicializa as ferramentas da NFe
     */
    private function initializeTools(): void
    {
        try {
            $configJson = json_encode($this->config);

            // Carrega certificado digital
            $certificatePath = \ROOT_PATH . '/storage/certificates/certificado.pfx';
            $certificatePassword = $_ENV['NFE_CERTIFICATE_PASSWORD'] ?? '';

            if (!file_exists($certificatePath)) {
                throw new Exception("Certificado digital não encontrado em: {$certificatePath}");
            }

            $certificate = Certificate::readPfx(
                file_get_contents($certificatePath),
                $certificatePassword
            );

            $this->tools = new Tools($configJson, $certificate);
            $this->tools->model('55'); // NFe modelo 55

        } catch (Exception $e) {
            error_log("Erro ao inicializar NFe Tools: " . $e->getMessage());
            throw $e;
        }
    }

    /**
     * Cria uma NFe
     *
     * @param array $data Dados da nota fiscal
     * @return array Resultado da operação
     */
    public function criarNFe(array $data): array
    {
        try {
            $nfe = new Make();

            // Tag IDE (Identificação da NFe)
            $std = new \stdClass();
            $std->cUF = $this->getCodigoUF($this->config['siglaUF']);
            $std->cNF = rand(10000000, 99999999); // Código numérico aleatório
            $std->natOp = $data['natureza_operacao'] ?? 'VENDA DE MERCADORIA';
            $std->mod = 55; // Modelo NFe
            $std->serie = $data['serie'] ?? 1;
            $std->nNF = $data['numero_nfe'];
            $std->dhEmi = date('Y-m-d\TH:i:sP');
            $std->tpNF = 1; // 0-Entrada, 1-Saída
            $std->idDest = 1; // 1-Operação interna, 2-Interestadual, 3-Exterior
            $std->cMunFG = $data['codigo_municipio'] ?? '3550308'; // São Paulo
            $std->tpImp = 1; // 1-DANFE normal, 2-DANFE simplificado
            $std->tpEmis = 1; // 1-Emissão normal
            $std->cDV = 0; // Será calculado
            $std->tpAmb = $this->config['tpAmb'];
            $std->finNFe = 1; // 1-Normal, 2-Complementar, 3-Ajuste, 4-Devolução
            $std->indFinal = $data['consumidor_final'] ?? 0; // 0-Normal, 1-Consumidor final
            $std->indPres = 1; // 1-Operação presencial
            $std->procEmi = 0; // 0-Emissão própria
            $std->verProc = '1.0.0';

            $nfe->taginfNFe($std);

            // Tag EMIT (Emitente)
            $std = new \stdClass();
            $std->CNPJ = $this->config['cnpj'];
            $std->xNome = $this->config['razaosocial'];
            $std->xFant = $data['emitente_fantasia'] ?? $this->config['razaosocial'];
            $std->IE = $data['emitente_ie'] ?? '';
            $std->CRT = $data['emitente_crt'] ?? 1; // 1-Simples Nacional
            $nfe->tagemit($std);

            // Endereço do Emitente
            $std = new \stdClass();
            $std->xLgr = $data['emitente_logradouro'] ?? '';
            $std->nro = $data['emitente_numero'] ?? '';
            $std->xBairro = $data['emitente_bairro'] ?? '';
            $std->cMun = $data['codigo_municipio'] ?? '3550308';
            $std->xMun = $data['emitente_municipio'] ?? 'São Paulo';
            $std->UF = $this->config['siglaUF'];
            $std->CEP = $data['emitente_cep'] ?? '';
            $std->cPais = '1058';
            $std->xPais = 'BRASIL';
            $std->fone = $data['emitente_telefone'] ?? '';
            $nfe->tagenderEmit($std);

            // Tag DEST (Destinatário)
            $std = new \stdClass();
            if (!empty($data['destinatario_cnpj'])) {
                $std->CNPJ = $data['destinatario_cnpj'];
            } else {
                $std->CPF = $data['destinatario_cpf'];
            }
            $std->xNome = $data['destinatario_nome'];
            $std->indIEDest = 9; // 9-Não contribuinte
            $nfe->tagdest($std);

            // Endereço do Destinatário
            if (!empty($data['destinatario_logradouro'])) {
                $std = new \stdClass();
                $std->xLgr = $data['destinatario_logradouro'];
                $std->nro = $data['destinatario_numero'] ?? 'S/N';
                $std->xBairro = $data['destinatario_bairro'];
                $std->cMun = $data['destinatario_codigo_municipio'];
                $std->xMun = $data['destinatario_municipio'];
                $std->UF = $data['destinatario_uf'];
                $std->CEP = $data['destinatario_cep'];
                $std->cPais = '1058';
                $std->xPais = 'BRASIL';
                $nfe->tagenderDest($std);
            }

            // Produtos/Itens
            $itemNumber = 1;
            foreach ($data['itens'] as $item) {
                // Tag PROD (Produto)
                $std = new \stdClass();
                $std->item = $itemNumber;
                $std->cProd = $item['codigo'];
                $std->cEAN = $item['ean'] ?? 'SEM GTIN';
                $std->xProd = $item['descricao'];
                $std->NCM = $item['ncm'] ?? '00000000';
                $std->CFOP = $item['cfop'] ?? '5102';
                $std->uCom = $item['unidade'] ?? 'UN';
                $std->qCom = $item['quantidade'];
                $std->vUnCom = $item['valor_unitario'];
                $std->vProd = $item['valor_total'];
                $std->cEANTrib = $item['ean'] ?? 'SEM GTIN';
                $std->uTrib = $item['unidade'] ?? 'UN';
                $std->qTrib = $item['quantidade'];
                $std->vUnTrib = $item['valor_unitario'];
                $std->indTot = 1; // 1-Compõe total da NF
                $nfe->tagprod($std);

                // Tag IMPOSTO (Impostos do Item)
                $std = new \stdClass();
                $std->item = $itemNumber;
                $nfe->tagimposto($std);

                // ICMS (Simples Nacional)
                $std = new \stdClass();
                $std->item = $itemNumber;
                $std->orig = 0; // 0-Nacional
                $std->CSOSN = '102'; // 102-Tributada pelo Simples Nacional sem permissão de crédito
                $nfe->tagICMSSN($std);

                // PIS
                $std = new \stdClass();
                $std->item = $itemNumber;
                $std->CST = '99'; // 99-Outras operações
                $nfe->tagPIS($std);

                // COFINS
                $std = new \stdClass();
                $std->item = $itemNumber;
                $std->CST = '99'; // 99-Outras operações
                $nfe->tagCOFINS($std);

                $itemNumber++;
            }

            // Tag ICMSTot (Totais dos impostos)
            $std = new \stdClass();
            $std->vBC = 0.00;
            $std->vICMS = 0.00;
            $std->vICMSDeson = 0.00;
            $std->vFCP = 0.00;
            $std->vBCST = 0.00;
            $std->vST = 0.00;
            $std->vFCPST = 0.00;
            $std->vFCPSTRet = 0.00;
            $std->vProd = $data['valor_produtos'];
            $std->vFrete = $data['valor_frete'] ?? 0.00;
            $std->vSeg = 0.00;
            $std->vDesc = $data['valor_desconto'] ?? 0.00;
            $std->vII = 0.00;
            $std->vIPI = 0.00;
            $std->vIPIDevol = 0.00;
            $std->vPIS = 0.00;
            $std->vCOFINS = 0.00;
            $std->vOutro = 0.00;
            $std->vNF = $data['valor_total'];
            $std->vTotTrib = 0.00;
            $nfe->tagICMSTot($std);

            // Tag TRANSP (Transporte)
            $std = new \stdClass();
            $std->modFrete = $data['modalidade_frete'] ?? 9; // 9-Sem frete
            $nfe->tagtransp($std);

            // Tag PAGO (Pagamento)
            $std = new \stdClass();
            $std->vTroco = 0.00;
            $nfe->tagpag($std);

            $std = new \stdClass();
            $std->tPag = $data['forma_pagamento'] ?? '01'; // 01-Dinheiro
            $std->vPag = $data['valor_total'];
            $nfe->tagdetPag($std);

            // Tag infAdic (Informações Adicionais)
            if (!empty($data['informacoes_complementares'])) {
                $std = new \stdClass();
                $std->infCpl = $data['informacoes_complementares'];
                $nfe->taginfAdic($std);
            }

            // Gera o XML
            $xml = $nfe->getXML();

            // Assina o XML
            $xmlAssinado = $this->tools->signNFe($xml);

            // Envia para a SEFAZ
            $response = $this->tools->sefazEnviaLote([$xmlAssinado], 1);

            // Processa a resposta
            $stdCl = new Standardize($response);
            $std = $stdCl->toStd();

            if ($std->cStat != 103) {
                throw new Exception("Erro ao enviar NFe: {$std->xMotivo}");
            }

            // Consulta o recibo
            $recibo = $std->infRec->nRec;
            sleep(2); // Aguarda processamento

            $responseConsulta = $this->tools->sefazConsultaRecibo($recibo);
            $stdConsulta = $stdCl->toStd($responseConsulta);

            if ($stdConsulta->cStat != 104) {
                throw new Exception("Erro no processamento: {$stdConsulta->xMotivo}");
            }

            $protNFe = $stdConsulta->protNFe;
            $chaveNFe = $protNFe->infProt->chNFe;

            // Adiciona o protocolo ao XML
            $xmlProtocolado = $this->tools->addProtocol($xmlAssinado, $responseConsulta);

            // Salva o XML
            $this->salvarXML($xmlProtocolado, $chaveNFe);

            return [
                'success' => true,
                'chave' => $chaveNFe,
                'numero_protocolo' => $protNFe->infProt->nProt,
                'xml' => $xmlProtocolado
            ];

        } catch (Exception $e) {
            error_log("Erro ao criar NFe: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }

    /**
     * Salva o XML da NFe
     */
    private function salvarXML(string $xml, string $chave): void
    {
        $dir = \ROOT_PATH . '/storage/nfe/' . date('Ym');

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

        file_put_contents($dir . '/' . $chave . '-nfe.xml', $xml);
    }

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

        return $ufs[$sigla] ?? 0;
    }

    /**
     * Cancela uma NFe
     */
    public function cancelarNFe(string $chave, string $justificativa, string $numeroProtocolo): array
    {
        try {
            if (strlen($justificativa) < 15) {
                throw new Exception("Justificativa deve ter no mínimo 15 caracteres");
            }

            $response = $this->tools->sefazCancela($chave, $justificativa, $numeroProtocolo);

            $stdCl = new Standardize($response);
            $std = $stdCl->toStd();

            if ($std->cStat != 135) {
                throw new Exception("Erro ao cancelar: {$std->xMotivo}");
            }

            return [
                'success' => true,
                'protocolo_cancelamento' => $std->infCanc->nProt
            ];

        } catch (Exception $e) {
            error_log("Erro ao cancelar NFe: " . $e->getMessage());
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
}

