<?php

namespace App\Services\Sefaz;

use App\Models\Company;
use App\Models\NotaFiscal;
use Illuminate\Support\Carbon;

/**
 * Manifestação do destinatário (evento 210xxx).
 *
 * Quando a SEFAZ entrega apenas o resumo (resNFe) de uma nota, o XML completo
 * só é liberado depois que o destinatário registra a "Ciência da Operação".
 * É um ato fiscal: fica gravado na SEFAZ e não pode ser desfeito, por isso a
 * automação só roda quando a empresa habilita `manifestacao_automatica`.
 */
class ManifestacaoService
{
    public function __construct(private readonly SefazToolsFactory $tools) {}

    /**
     * @return array{ok: bool, cstat: string, motivo: string, protocolo: ?string}
     */
    public function manifestar(Company $company, NotaFiscal $nota, string $tipo = 'ciencia', string $justificativa = ''): array
    {
        $codigos = config('nfe.manifestacao');

        if (! isset($codigos[$tipo])) {
            throw new SefazException("Tipo de manifestação inválido: {$tipo}");
        }

        if ($tipo === 'nao_realizada' && mb_strlen($justificativa) < 15) {
            throw new SefazException('A operação não realizada exige justificativa com no mínimo 15 caracteres.');
        }

        $resposta = $this->tools->make($company)->sefazManifesta(
            $nota->chave,
            $codigos[$tipo],
            $justificativa,
            1,
        );

        $sx = @simplexml_load_string($resposta);

        if ($sx === false) {
            throw new SefazException('Resposta da SEFAZ em formato inesperado.');
        }

        $ret = $sx->xpath('//*[local-name()="retEvento"]/*[local-name()="infEvento"]')[0] ?? null;
        $cstat = (string) ($ret->cStat ?? $sx->cStat ?? '');
        $motivo = (string) ($ret->xMotivo ?? $sx->xMotivo ?? 'Sem retorno da SEFAZ.');

        // 135 = evento registrado, 136 = registrado fora de prazo,
        // 573 = duplicidade (já manifestado antes) — todos são sucesso para nós.
        $ok = in_array($cstat, ['135', '136', '573'], true);

        if ($ok) {
            $nota->forceFill([
                'manifestacao' => $tipo,
                'manifestacao_at' => now(),
                'manifestacao_protocolo' => (string) ($ret->nProt ?? '') ?: null,
            ])->save();
        }

        return [
            'ok' => $ok,
            'cstat' => $cstat,
            'motivo' => $motivo,
            'protocolo' => (string) ($ret->nProt ?? '') ?: null,
        ];
    }

    /**
     * Manifesta ciência em lote nas notas que ainda estão como resumo.
     * O prazo legal para manifestar é de 180 dias da emissão.
     *
     * @return array{sucesso: int, falha: int}
     */
    public function darCienciaPendentes(Company $company, ?int $limite = null): array
    {
        $limite ??= (int) config('nfe.max_manifestacoes_por_ciclo');

        $pendentes = NotaFiscal::withoutGlobalScopes()
            ->where('company_id', $company->id)
            ->pendentesManifestacao()
            ->where('data_emissao', '>=', Carbon::now()->subDays(180))
            ->orderByDesc('data_emissao')
            ->limit($limite)
            ->get();

        $sucesso = 0;
        $falha = 0;

        foreach ($pendentes as $nota) {
            try {
                $this->manifestar($company, $nota, 'ciencia')['ok'] ? $sucesso++ : $falha++;
            } catch (\Throwable) {
                $falha++;
            }

            usleep((int) config('nfe.pausa_entre_requisicoes_ms') * 1000);
        }

        return ['sucesso' => $sucesso, 'falha' => $falha];
    }
}
