<?php

namespace App\Services;

use App\Models\Company;
use App\Models\NotaEvento;
use App\Models\NotaFiscal;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use ZipArchive;

/**
 * Monta o pacote .zip de XMLs para a contabilidade.
 *
 * Organização do arquivo:
 *
 *   2026/01/3524...xml          notas autorizadas, por competência
 *   CANCELADAS/2026/01/...xml   fora das autorizadas, para não escriturar por engano
 *   DENEGADAS/2026/01/...xml
 *   EVENTOS/3524...-110110-1.xml
 *   relacao.csv                 conferência: chave, fornecedor, valor, situação
 *
 * A competência pode seguir a data de emissão (padrão) ou a data em que o
 * documento foi recebido — escritórios que escrituram pela entrada precisam
 * do segundo critério, e as duas datas divergem na virada do mês.
 */
class PacoteXmlService
{
    /** Teto de notas por pacote, para não estourar o tempo de execução do cPanel. */
    public const MAX_NOTAS = 2000;

    /**
     * @param  Collection<int, NotaFiscal>  $notas
     * @return array{caminho: string, nome: string, arquivos: int}
     */
    public function montar(
        Collection $notas,
        ?Company $company,
        string $agrupar = 'emissao',
        bool $incluirEventos = true,
    ): array {
        if ($notas->isEmpty()) {
            throw new RuntimeException('Nenhum XML disponível para os filtros selecionados.');
        }

        $temp = tempnam(sys_get_temp_dir(), 'xmls_');
        $zip = new ZipArchive;

        if ($zip->open($temp, ZipArchive::OVERWRITE) !== true) {
            throw new RuntimeException('Não foi possível montar o pacote de download.');
        }

        $disco = Storage::disk('xmls');
        $arquivos = 0;
        $relacao = [];
        $ausentes = 0;

        foreach ($notas as $nota) {
            if (! $nota->xml_path || ! $disco->exists($nota->xml_path)) {
                $ausentes++;
                $relacao[] = $this->linhaRelacao($nota, '', 'arquivo não encontrado no servidor');

                continue;
            }

            $caminho = $this->pastaDaNota($nota, $agrupar).'/'.$nota->chave.'.xml';

            $zip->addFromString($caminho, $disco->get($nota->xml_path));
            $arquivos++;

            $relacao[] = $this->linhaRelacao($nota, $caminho, 'ok');
        }

        if ($incluirEventos) {
            $arquivos += $this->adicionarEventos($zip, $notas, $disco);
        }

        $zip->addFromString('relacao.csv', $this->csv($relacao));
        $zip->addFromString('LEIA-ME.txt', $this->leiaMe($notas, $agrupar, $arquivos, $ausentes));

        $zip->close();

        return [
            'caminho' => $temp,
            'nome' => sprintf('xmls-%s-%s.zip', $company?->cnpj ?? 'geral', now()->format('Ymd-His')),
            'arquivos' => $arquivos,
        ];
    }

    /**
     * Notas canceladas e denegadas saem das pastas de competência. Elas não
     * geram crédito e não devem ser escrituradas — separar evita o erro.
     */
    private function pastaDaNota(NotaFiscal $nota, string $agrupar): string
    {
        $data = $agrupar === 'recebimento'
            ? ($nota->created_at ?? $nota->data_emissao)
            : ($nota->data_emissao ?? $nota->created_at);

        $competencia = $data?->format('Y/m') ?: 'sem-data';

        return match ($nota->situacao) {
            'cancelada' => 'CANCELADAS/'.$competencia,
            'denegada' => 'DENEGADAS/'.$competencia,
            default => $competencia,
        };
    }

    /**
     * Carta de correção e cancelamento fazem parte do documento fiscal: sem
     * eles a escrituração fica incompleta.
     *
     * @param  Collection<int, NotaFiscal>  $notas
     */
    private function adicionarEventos(ZipArchive $zip, Collection $notas, $disco): int
    {
        $eventos = NotaEvento::withoutGlobalScopes()
            ->whereIn('chave', $notas->pluck('chave'))
            ->whereIn('company_id', $notas->pluck('company_id')->unique())
            ->whereNotNull('xml_path')
            ->get();

        $total = 0;

        foreach ($eventos as $evento) {
            if (! $disco->exists($evento->xml_path)) {
                continue;
            }

            $zip->addFromString(
                sprintf('EVENTOS/%s-%s-%d.xml', $evento->chave, $evento->tipo_evento, $evento->sequencia),
                $disco->get($evento->xml_path),
            );
            $total++;
        }

        return $total;
    }

    private function linhaRelacao(NotaFiscal $nota, string $caminho, string $situacaoArquivo): array
    {
        return [
            $nota->chave,
            $nota->numero,
            $nota->serie,
            $nota->data_emissao?->format('d/m/Y') ?: '',
            $nota->created_at?->format('d/m/Y') ?: '',
            $nota->emit_cnpj,
            $nota->emit_nome,
            number_format((float) $nota->valor_total, 2, ',', ''),
            $nota->situacaoLabel(),
            $nota->manifestacaoLabel(),
            $caminho,
            $situacaoArquivo,
        ];
    }

    private function csv(array $linhas): string
    {
        $cabecalho = [
            'chave', 'numero', 'serie', 'emissao', 'recebimento', 'cnpj_emitente',
            'nome_emitente', 'valor_total', 'situacao', 'manifestacao', 'arquivo', 'observacao',
        ];

        $saida = fopen('php://temp', 'r+');

        // Ponto e vírgula e BOM: é assim que o Excel em português abre o
        // arquivo com as colunas separadas e os acentos corretos.
        fwrite($saida, "\xEF\xBB\xBF");
        fputcsv($saida, $cabecalho, ';');

        foreach ($linhas as $linha) {
            fputcsv($saida, $linha, ';');
        }

        rewind($saida);
        $conteudo = stream_get_contents($saida);
        fclose($saida);

        return $conteudo;
    }

    private function leiaMe(Collection $notas, string $agrupar, int $arquivos, int $ausentes): string
    {
        $criterio = $agrupar === 'recebimento'
            ? 'data de recebimento do documento neste sistema'
            : 'data de emissão da nota';

        $canceladas = $notas->where('situacao', 'cancelada')->count();
        $denegadas = $notas->where('situacao', 'denegada')->count();

        $texto = [
            'PACOTE DE XMLs DE COMPRA',
            'Gerado em '.now()->format('d/m/Y H:i'),
            '',
            'ORGANIZAÇÃO',
            '  AAAA/MM/            notas autorizadas, agrupadas pela '.$criterio,
            '  CANCELADAS/AAAA/MM/ notas canceladas — não devem ser escrituradas',
            '  DENEGADAS/AAAA/MM/  notas denegadas — não geram crédito',
            '  EVENTOS/            cartas de correção e cancelamentos',
            '  relacao.csv         conferência item a item (abre no Excel)',
            '',
            'RESUMO',
            '  Notas no pacote ...... '.$notas->count(),
            '  Arquivos gravados .... '.$arquivos,
            '  Canceladas ........... '.$canceladas,
            '  Denegadas ............ '.$denegadas,
        ];

        if ($ausentes > 0) {
            $texto[] = '  Sem arquivo no servidor '.$ausentes.' (veja a coluna "observacao" do relacao.csv)';
        }

        $texto[] = '';
        $texto[] = 'A guarda dos XMLs é obrigatória por 5 anos. Este pacote é uma cópia:';
        $texto[] = 'os arquivos originais seguem arquivados no sistema.';

        return implode("\r\n", $texto);
    }
}
