<?php

namespace App\Services\Sefaz;

use App\Models\NotaEvento;
use Carbon\Carbon;
use SimpleXMLElement;

/**
 * Traduz os documentos devolvidos pelo NFeDistribuicaoDFe em arrays prontos
 * para persistência. A SEFAZ entrega quatro formatos distintos:
 *
 *  - resNFe        resumo da NF-e (só os campos essenciais)
 *  - procNFe       XML completo autorizado (nfeProc)
 *  - resEvento     resumo de evento
 *  - procEventoNFe evento completo
 */
class DfeParser
{
    /**
     * @return array{kind: string, data: array}|null
     */
    public function parse(string $xml, ?int $nsu = null, ?string $schema = null): ?array
    {
        $sx = @simplexml_load_string($xml);

        if ($sx === false) {
            return null;
        }

        $root = $sx->getName();

        return match ($root) {
            'resNFe' => ['kind' => 'nota', 'data' => $this->fromResumoNota($sx, $nsu, $schema)],
            'nfeProc' => ['kind' => 'nota', 'data' => $this->fromProcNota($sx, $nsu, $schema)],
            'resEvento' => ['kind' => 'evento', 'data' => $this->fromResumoEvento($sx, $nsu)],
            'procEventoNFe' => ['kind' => 'evento', 'data' => $this->fromProcEvento($sx, $nsu)],
            default => null,
        };
    }

    private function fromResumoNota(SimpleXMLElement $sx, ?int $nsu, ?string $schema): array
    {
        return [
            'chave' => (string) $sx->chNFe,
            'nsu' => $nsu,
            'schema_dfe' => $schema,
            'tipo' => 'resumo',
            'tem_xml_completo' => false,
            'emit_cnpj' => $this->digits((string) $sx->CNPJ ?: (string) $sx->CPF),
            'emit_nome' => (string) $sx->xNome,
            'emit_ie' => (string) $sx->IE ?: null,
            'data_emissao' => $this->date((string) $sx->dhEmi),
            'valor_total' => (float) ((string) $sx->vNF ?: 0),
            'digest_value' => (string) $sx->digVal ?: null,
            'protocolo' => (string) $sx->nProt ?: null,
            'data_autorizacao' => $this->date((string) $sx->dhRecbto),
            'situacao' => $this->situacaoPorCstat((string) $sx->cSitNFe),
            ...$this->fromChave((string) $sx->chNFe),
        ];
    }

    private function fromProcNota(SimpleXMLElement $sx, ?int $nsu, ?string $schema): array
    {
        $inf = $sx->NFe->infNFe;
        $ide = $inf->ide;
        $emit = $inf->emit;
        $dest = $inf->dest;
        $prot = $sx->protNFe->infProt ?? null;

        $chave = str_replace('NFe', '', (string) $inf['Id']);

        return [
            'chave' => $chave,
            'nsu' => $nsu,
            'schema_dfe' => $schema,
            'tipo' => 'completo',
            'tem_xml_completo' => true,
            'emit_cnpj' => $this->digits((string) $emit->CNPJ ?: (string) $emit->CPF),
            'emit_nome' => (string) $emit->xNome,
            'emit_ie' => (string) $emit->IE ?: null,
            'emit_uf' => (string) ($emit->enderEmit->UF ?? '') ?: null,
            'dest_cnpj' => $this->digits((string) ($dest->CNPJ ?? '') ?: (string) ($dest->CPF ?? '')),
            'dest_nome' => (string) ($dest->xNome ?? '') ?: null,
            'modelo' => (string) $ide->mod,
            'serie' => (string) $ide->serie,
            'numero' => (int) $ide->nNF,
            'data_emissao' => $this->date((string) $ide->dhEmi),
            'valor_total' => (float) ((string) ($inf->total->ICMSTot->vNF ?? 0)),
            'digest_value' => $prot ? ((string) $prot->digVal ?: null) : null,
            'protocolo' => $prot ? ((string) $prot->nProt ?: null) : null,
            'data_autorizacao' => $prot ? $this->date((string) $prot->dhRecbto) : null,
            'situacao' => $prot ? $this->situacaoPorCstat((string) $prot->cStat) : 'autorizada',
        ];
    }

    private function fromResumoEvento(SimpleXMLElement $sx, ?int $nsu): array
    {
        $tipo = (string) $sx->tpEvento;

        return [
            'chave' => (string) $sx->chNFe,
            'nsu' => $nsu,
            'tipo_evento' => $tipo,
            'sequencia' => (int) ((string) $sx->nSeqEvento ?: 1),
            'descricao' => (string) $sx->xEvento ?: NotaEvento::descricaoPara($tipo),
            'data_evento' => $this->date((string) $sx->dhEvento),
            'protocolo' => (string) $sx->nProt ?: null,
        ];
    }

    private function fromProcEvento(SimpleXMLElement $sx, ?int $nsu): array
    {
        $inf = $sx->evento->infEvento;
        $tipo = (string) $inf->tpEvento;

        return [
            'chave' => (string) $inf->chNFe,
            'nsu' => $nsu,
            'tipo_evento' => $tipo,
            'sequencia' => (int) ((string) $inf->nSeqEvento ?: 1),
            'descricao' => (string) ($inf->detEvento->xEvento ?? '') ?: NotaEvento::descricaoPara($tipo),
            'data_evento' => $this->date((string) $inf->dhEvento),
            'protocolo' => (string) ($sx->retEvento->infEvento->nProt ?? '') ?: null,
        ];
    }

    /**
     * A chave de acesso carrega modelo, série e número — útil para exibir
     * esses dados mesmo quando só recebemos o resumo da nota.
     */
    private function fromChave(string $chave): array
    {
        if (strlen($chave) !== 44) {
            return [];
        }

        return [
            'emit_uf' => $this->ufPorCodigo(substr($chave, 0, 2)),
            'modelo' => substr($chave, 20, 2),
            'serie' => ltrim(substr($chave, 22, 3), '0') ?: '0',
            'numero' => (int) substr($chave, 25, 9),
        ];
    }

    /**
     * Situação do documento.
     *
     * No resumo (resNFe) o campo é cSitNFe: 1 autorizada, 2 denegada, 3 cancelada.
     * No XML completo é o cStat do protocolo de autorização:
     *   100 autorizado · 150 autorizado fora de prazo
     *   101 cancelado  · 151 e 155 cancelados fora de prazo
     *   110, 301, 302, 303 denegados
     *
     * O 135 ("evento registrado") não aparece em protNFe — só em retEvento —
     * e por isso não entra aqui.
     */
    private function situacaoPorCstat(string $cstat): string
    {
        return match ($cstat) {
            '3', '101', '151', '155' => 'cancelada',
            '2', '110', '301', '302', '303' => 'denegada',
            default => 'autorizada',
        };
    }

    private function digits(?string $value): ?string
    {
        $value = preg_replace('/\D/', '', (string) $value);

        return $value === '' ? null : $value;
    }

    private function date(?string $value): ?Carbon
    {
        if (blank($value)) {
            return null;
        }

        try {
            return Carbon::parse($value);
        } catch (\Throwable) {
            return null;
        }
    }

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