<?php

namespace App\Support;

use Illuminate\Support\Facades\Storage;
use Throwable;

/**
 * Requisitos do servidor, em um lugar só.
 *
 * O comando `xml:diagnostico` e o instalador web leem daqui — se a lista
 * vivesse duplicada, uma das duas telas passaria a mentir na primeira mudança.
 */
class Requisitos
{
    /** Extensões do PHP sem as quais alguma parte do sistema não funciona. */
    public const EXTENSOES = [
        'openssl' => 'ler o certificado A1 e assinar as consultas à SEFAZ',
        'soap' => 'comunicação com o NFeDistribuicaoDFe',
        'curl' => 'transporte HTTP das chamadas',
        'dom' => 'leitura e validação dos XMLs',
        'simplexml' => 'interpretação dos documentos fiscais',
        'libxml' => 'base do parser de XML',
        'zlib' => 'descompactar os documentos (a SEFAZ entrega em gzip)',
        'zip' => 'montar o pacote .zip de download',
        'gd' => 'gerar o código de barras do DANFE',
        'mbstring' => 'tratamento de acentuação',
        'intl' => 'formatação de valores em real',
        'fileinfo' => 'validar os arquivos enviados',
        'bcmath' => 'cálculos de precisão nos valores fiscais',
        'json' => 'serialização interna',
        'pdo_mysql' => 'conexão com o MySQL/MariaDB',
    ];

    /**
     * @return array<int, array{item: string, ok: bool, valor: string, ajuda: string, critico: bool}>
     */
    public static function verificarAmbiente(): array
    {
        $itens = [];

        $itens[] = self::item(
            'PHP 8.3 ou superior',
            version_compare(PHP_VERSION, '8.3.0', '>='),
            PHP_VERSION,
            'Selecione o PHP 8.3+ em "Select PHP Version" no cPanel.',
        );

        foreach (self::EXTENSOES as $ext => $para) {
            $itens[] = self::item(
                "Extensão {$ext}",
                extension_loaded($ext),
                extension_loaded($ext) ? 'ativa' : 'ausente',
                "Necessária para {$para}. Ative em \"Select PHP Version → Extensions\".",
            );
        }

        $limite = (int) ini_get('max_execution_time');
        $itens[] = self::item(
            'Tempo de execução',
            $limite === 0 || $limite >= 60,
            $limite === 0 ? 'sem limite' : $limite.'s',
            'Menos de 60s pode cortar sincronizações longas e pacotes .zip grandes.',
            critico: false,
        );

        return $itens;
    }

    /**
     * @return array<int, array{item: string, ok: bool, valor: string, ajuda: string, critico: bool}>
     */
    public static function verificarEscrita(): array
    {
        $itens = [];

        foreach ([
            'storage/framework' => storage_path('framework'),
            'storage/logs' => storage_path('logs'),
            'bootstrap/cache' => base_path('bootstrap/cache'),
        ] as $rotulo => $caminho) {
            $gravavel = is_dir($caminho) && is_writable($caminho);

            $itens[] = self::item(
                "Escrita em {$rotulo}",
                $gravavel,
                is_dir($caminho) ? ($gravavel ? 'gravável' : 'somente leitura') : 'não existe',
                'Ajuste a permissão para 775 pelo File Manager do cPanel.',
            );
        }

        foreach (['certificados' => 'certificados A1', 'xmls' => 'documentos fiscais', 'branding' => 'logo e favicon'] as $disco => $para) {
            $itens[] = self::item(
                "Pasta de {$para}",
                self::discoGravavel($disco),
                self::discoGravavel($disco) ? 'gravável' : 'sem escrita',
                'Ajuste a permissão de storage/app para 775.',
            );
        }

        return $itens;
    }

    /**
     * Itens que não impedem instalar, mas que o operador precisa saber.
     *
     * @return array<int, array{item: string, ok: bool, valor: string, ajuda: string, critico: bool}>
     */
    public static function verificarPreparacao(): array
    {
        $certDir = realpath(storage_path('app/certificados')) ?: storage_path('app/certificados');
        $publico = realpath(public_path()) ?: public_path();

        return [
            self::item(
                'Arquivo .env gravável',
                file_exists(base_path('.env')) ? is_writable(base_path('.env')) : is_writable(base_path()),
                file_exists(base_path('.env')) ? 'presente' : 'será criado',
                'O instalador precisa gravar as configurações. Ajuste a permissão do arquivo para 664.',
            ),
            self::item(
                'Assets compilados',
                file_exists(public_path('build/manifest.json')),
                file_exists(public_path('build/manifest.json')) ? 'presentes' : 'ausentes',
                'Envie a pasta public/build para o public_html. Sem ela o sistema abre sem estilo.',
            ),
            self::item(
                'Certificados fora da pasta pública',
                ! str_starts_with($certDir, $publico),
                ! str_starts_with($certDir, $publico) ? 'protegidos' : 'EXPOSTOS',
                'GRAVE: a aplicação precisa ficar fora do public_html. Certificado exposto permite emitir nota em nome do cliente.',
            ),
        ];
    }

    /** @param array<int, array{ok: bool, critico: bool}> ...$grupos */
    public static function podeInstalar(array ...$grupos): bool
    {
        foreach ($grupos as $grupo) {
            foreach ($grupo as $item) {
                if ($item['critico'] && ! $item['ok']) {
                    return false;
                }
            }
        }

        return true;
    }

    private static function discoGravavel(string $disco): bool
    {
        try {
            Storage::disk($disco)->put('.check', 'ok');
            $ok = Storage::disk($disco)->get('.check') === 'ok';
            Storage::disk($disco)->delete('.check');

            return $ok;
        } catch (Throwable) {
            return false;
        }
    }

    private static function item(string $item, bool $ok, string $valor, string $ajuda, bool $critico = true): array
    {
        return compact('item', 'ok', 'valor', 'ajuda', 'critico');
    }
}
