<?php
declare(strict_types=1);

namespace App\Services;

use App\Config\Config;
use App\Core\DB;

class SefazService
{
    public static function distribuicao(int $tenantId, string $cnpj, string $nsu = '000000'): array
    {
        $env = Config::get('env');
        $url = Config::get("sefaz.$env.distribuicao");
        $ufStmt = DB::pdo()->prepare("SELECT uf FROM tenants WHERE id = ?");
        $ufStmt->execute([$tenantId]);
        $uf = strtoupper((string)($ufStmt->fetch()['uf'] ?? ''));
        $cUF = self::ufToCode($uf);
        $soap = self::buildDistribuicaoSoap($cnpj, $nsu, $cUF);
        $cert = self::getCert($tenantId);
        $resp = self::postSoap($url, $soap, $cert);
        self::log($tenantId, 'distribuicao', $soap, $resp['body'] ?? '', $resp['status'] ?? 0);
        if (!empty($resp['body'])) {
            self::processDistribuicaoResposta($tenantId, $resp['body']);
        }
        return $resp;
    }

    public static function manifestar(int $tenantId, string $cnpj, string $chNFe, string $tipo): array
    {
        $env = Config::get('env');
        $url = Config::get("sefaz.$env.evento");
        $soap = self::buildManifestacaoSoap($cnpj, $chNFe, $tipo);
        $cert = self::getCert($tenantId);
        $resp = self::postSoap($url, $soap, $cert);
        self::log($tenantId, 'manifestacao', $soap, $resp['body'] ?? '', $resp['status'] ?? 0);
        $statusEvento = '';
        $protocolo = '';
        if (!empty($resp['body'])) {
            if (preg_match('/<cStat>(\\d+)<\\/cStat>/', (string)$resp['body'], $m)) {
                $statusEvento = $m[1];
            }
            if (preg_match('/<nProt>([^<]+)<\\/nProt>/', (string)$resp['body'], $m)) {
                $protocolo = $m[1];
            }
        }
        try {
            $stmt = DB::pdo()->prepare("INSERT INTO manifestacoes (tenant_id, chave, tipo, protocolo, status, created_at) VALUES (?, ?, ?, ?, ?, ?)");
            $stmt->execute([$tenantId, $chNFe, $tipo, $protocolo, $statusEvento ?: 'erro', date('Y-m-d H:i:s')]);
        } catch (\Throwable $e) {
        }
        return $resp;
    }

    public static function salvarXml(int $tenantId, string $chave, string $xml): string
    {
        $base = Config::get('storage_path') . '/tenants/' . $tenantId . '/xml';
        if (!is_dir($base)) {
            @mkdir($base, 0777, true);
        }
        $path = $base . '/' . $chave . '.xml';
        file_put_contents($path, $xml);
        return $path;
    }

    private static function postSoap(string $url, string $xml, array $cert): array
    {
        $attempts = 0;
        $lastStatus = 0;
        $lastBody = '';
        while ($attempts < 3) {
            $attempts++;
            $ch = curl_init($url);
            curl_setopt($ch, CURLOPT_POST, true);
            curl_setopt($ch, CURLOPT_HTTPHEADER, [
                'Content-Type: application/soap+xml; charset=utf-8',
                'SOAPAction: "http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe/nfeDistDFeInteresse"',
            ]);
            curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
            curl_setopt($ch, CURLOPT_TIMEOUT, 30);
            curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
            if ($cert['path']) {
                curl_setopt($ch, CURLOPT_SSLCERT, $cert['path']);
                curl_setopt($ch, CURLOPT_SSLCERTTYPE, 'P12');
                curl_setopt($ch, CURLOPT_SSLCERTPASSWD, $cert['password']);
            }
            $lastBody = (string)curl_exec($ch);
            $lastStatus = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE);
            $err = curl_error($ch);
            curl_close($ch);
            if ($lastStatus >= 200 && $lastStatus < 300 && $lastBody) {
                break;
            }
            $stmt = DB::pdo()->prepare("INSERT INTO sefaz_failed (tenant_id, operation, request, status_code, error, next_retry_at, retries, created_at) VALUES (NULL, 'soap', ?, ?, ?, ?, ?, ?)");
            $next = date('Y-m-d H:i:s', time() + 300);
            $stmt->execute([$xml, $lastStatus, $err, $next, $attempts, date('Y-m-d H:i:s')]);
            sleep(1);
        }
        return ['status' => $lastStatus, 'body' => $lastBody];
    }

    private static function getCert(int $tenantId): array
    {
        $stmt = DB::pdo()->prepare("SELECT cert_path, cert_password, cnpj FROM tenants WHERE id = ?");
        $stmt->execute([$tenantId]);
        $row = $stmt->fetch();
        return ['path' => $row['cert_path'] ?? '', 'password' => $row['cert_password'] ?? '', 'cnpj' => $row['cnpj'] ?? ''];
    }

    private static function buildDistribuicaoSoap(string $cnpj, string $nsu, int $cUFAutor): string
    {
        $tpAmb = Config::get('env') === 'producao' ? '1' : '2';
        $xml = '<?xml version="1.0" encoding="utf-8"?>' .
            '<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">' .
            '<soap12:Body>' .
            '<nfeDistDFeInteresse xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeDistribuicaoDFe">' .
            '<nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe">' .
            '<distDFeInt versao="1.01">' .
            '<tpAmb>' . $tpAmb . '</tpAmb>' .
            '<cUFAutor>' . $cUFAutor . '</cUFAutor>' .
            '<CNPJ>' . $cnpj . '</CNPJ>' .
            '<distNSU>' .
            '<ultNSU>' . $nsu . '</ultNSU>' .
            '</distNSU>' .
            '</distDFeInt>' .
            '</nfeDadosMsg>' .
            '</nfeDistDFeInteresse>' .
            '</soap12:Body>' .
            '</soap12:Envelope>';
        return $xml;
    }

    private static function buildManifestacaoSoap(string $cnpj, string $chNFe, string $tipo): string
    {
        $tpAmb = Config::get('env') === 'producao' ? '1' : '2';
        $evento = '';
        if ($tipo === 'confirmacao') {
            $evento = '210200';
        } elseif ($tipo === 'ciencia') {
            $evento = '210210';
        } elseif ($tipo === 'desconhecimento') {
            $evento = '210220';
        } elseif ($tipo === 'operacao_nao_realizada') {
            $evento = '210240';
        }
        $dt = date('Y-m-d\TH:i:sP');
        $xml = '<?xml version="1.0" encoding="utf-8"?>' .
            '<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">' .
            '<soap:Body>' .
            '<nfeRecepcaoEvento xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NFeRecepcaoEvento4">' .
            '<envEvento xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.00">' .
            '<idLote>1</idLote>' .
            '<evento versao="1.00">' .
            '<infEvento Id="ID' . $evento . $chNFe . '01">' .
            '<cOrgao>91</cOrgao>' .
            '<tpAmb>' . $tpAmb . '</tpAmb>' .
            '<CNPJ>' . $cnpj . '</CNPJ>' .
            '<chNFe>' . $chNFe . '</chNFe>' .
            '<dhEvento>' . $dt . '</dhEvento>' .
            '<tpEvento>' . $evento . '</tpEvento>' .
            '<nSeqEvento>1</nSeqEvento>' .
            '<verEvento>1.00</verEvento>' .
            '<detEvento versao="1.00">' .
            '<descEvento>Manifestacao</descEvento>' .
            '</detEvento>' .
            '</infEvento>' .
            '</evento>' .
            '</envEvento>' .
            '</nfeRecepcaoEvento>' .
            '</soap:Body>' .
            '</soap:Envelope>';
        return $xml;
    }

    private static function log(int $tenantId, string $operation, string $request, string $response, int $status): void
    {
        $stmt = DB::pdo()->prepare("INSERT INTO sefaz_logs (tenant_id, operation, request, response, status_code, created_at) VALUES (?, ?, ?, ?, ?, ?)");
        $stmt->execute([$tenantId, $operation, $request, $response, $status, date('Y-m-d H:i:s')]);
    }

    private static function processDistribuicaoResposta(int $tenantId, string $soapBody): void
    {
        $tenantStmt = DB::pdo()->prepare("SELECT t.cnpj, t.status, p.limit_notas FROM tenants t LEFT JOIN plans p ON t.plan_id = p.id WHERE t.id = ?");
        $tenantStmt->execute([$tenantId]);
        $tenant = $tenantStmt->fetch();
        if (!$tenant || ($tenant['status'] ?? '') !== 'ativo') {
            return;
        }
        $limit = (int)($tenant['limit_notas'] ?? 0);
        $nowMonth = date('Y-m');
        $countMonth = 0;
        if ($limit > 0) {
            $countStmt = DB::pdo()->prepare("SELECT COUNT(*) AS c FROM invoices WHERE tenant_id = ? AND DATE_FORMAT(data_emissao, '%Y-%m') = ?");
            try {
                $countStmt->execute([$tenantId, $nowMonth]);
            } catch (\Throwable $e) {
                $countStmt = DB::pdo()->prepare("SELECT COUNT(*) AS c FROM invoices WHERE tenant_id = ? AND strftime('%Y-%m', data_emissao) = ?");
                $countStmt->execute([$tenantId, $nowMonth]);
            }
            $countMonth = (int)($countStmt->fetch()['c'] ?? 0);
        }
        $matches = [];
        if (preg_match_all('/<docZip[^>]*>([^<]+)<\\/docZip>/', $soapBody, $matches)) {
            foreach ($matches[1] as $zip) {
                $bin = base64_decode($zip);
                $xml = function_exists('gzdecode') ? @gzdecode($bin) : @gzinflate(substr($bin, 10));
                if (!$xml) {
                    continue;
                }
                $chave = '';
                if (preg_match('/<chNFe>(\\d+)<\\/chNFe>/', $xml, $m)) {
                    $chave = $m[1];
                } elseif (preg_match('/Id=\"NFe(\\d+)\"/', $xml, $m)) {
                    $chave = $m[1];
                }
                if ($chave) {
                    if ($limit > 0 && $countMonth >= $limit) {
                        continue;
                    }
                    $path = self::salvarXml($tenantId, $chave, $xml);
                    $fields = \App\Services\XMLService::extractFields($xml);
                    $now = date('Y-m-d H:i:s');
                    $stmt = DB::pdo()->prepare("INSERT INTO invoices (tenant_id, chave, xml_path, emitente, destinatario, valor_total, data_emissao, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, 'baixado', ?, ?)");
                    $stmt->execute([$tenantId, $chave, $path, $fields['emitente'] ?? '', $fields['destinatario'] ?? '', $fields['valor_total'] ?? 0, $fields['data_emissao'] ?? null, $now, $now]);
                    $countMonth++;
                    if (!empty($tenant['cnpj'])) {
                        self::manifestar($tenantId, $tenant['cnpj'], $chave, 'ciencia');
                        $up = DB::pdo()->prepare("UPDATE invoices SET status = 'ciencia' WHERE tenant_id = ? AND chave = ?");
                        $up->execute([$tenantId, $chave]);
                    }
                }
            }
        }
        if (preg_match('/<ultNSU>(\\d+)<\\/ultNSU>/', $soapBody, $m)) {
            $nsu = $m[1];
            $stmt = DB::pdo()->prepare("UPDATE tenants SET nsu = ?, updated_at = ? WHERE id = ?");
            $stmt->execute([$nsu, date('Y-m-d H:i:s'), $tenantId]);
        }
    }

    public static function distribuicaoLoop(int $tenantId, string $cnpj, string $startNsu, int $maxIter = 10): void
    {
        $ufStmt = DB::pdo()->prepare("SELECT uf FROM tenants WHERE id = ?");
        $ufStmt->execute([$tenantId]);
        $uf = strtoupper((string)($ufStmt->fetch()['uf'] ?? ''));
        $cUF = self::ufToCode($uf);
        $env = Config::get('env');
        $url = Config::get("sefaz.$env.distribuicao");
        $cert = self::getCert($tenantId);
        $nsu = $startNsu;
        for ($i = 0; $i < $maxIter; $i++) {
            $soap = self::buildDistribuicaoSoap($cnpj, $nsu, $cUF);
            $resp = self::postSoap($url, $soap, $cert);
            self::log($tenantId, 'distribuicao', $soap, $resp['body'] ?? '', $resp['status'] ?? 0);
            if (empty($resp['body'])) {
                break;
            }
            self::processDistribuicaoResposta($tenantId, $resp['body']);
            if (preg_match('/<ultNSU>(\\d+)<\\/ultNSU>/', (string)$resp['body'], $m)) {
                $newNsu = $m[1];
                if ($newNsu === $nsu) {
                    break;
                }
                $nsu = $newNsu;
            } else {
                break;
            }
        }
    }

    private static function ufToCode(string $uf): int
    {
        $map = [
            'RO'=>11,'AC'=>12,'AM'=>13,'RR'=>14,'PA'=>15,'AP'=>16,'TO'=>17,
            'MA'=>21,'PI'=>22,'CE'=>23,'RN'=>24,'PB'=>25,'PE'=>26,'AL'=>27,'SE'=>28,'BA'=>29,
            'MG'=>31,'ES'=>32,'RJ'=>33,'SP'=>35,
            'PR'=>41,'SC'=>42,'RS'=>43,
            'MS'=>50,'MT'=>51,'GO'=>52,'DF'=>53
        ];
        return $map[$uf] ?? 91;
    }
}
