<?php

namespace App\Services;

use App\Core\Database;

/**
 * Executa consultas SQL no Firebird de forma controlada e segura.
 *
 * Regras de segurança:
 * - Somente SELECT é permitido
 * - EXECUTE, BEGIN, múltiplos comandos são bloqueados
 * - Resultado limitado a 1000 linhas (proteção contra dump total)
 * - BLOBs são convertidos para string legível
 */
class SqlConsoleService
{
    private const LIMITE_LINHAS = 1000;

    // -------------------------------------------------------------------------
    // Validação de segurança
    // -------------------------------------------------------------------------

    /**
     * Verifica se o SQL é permitido.
     *
     * @return array{permitido: bool, motivo: string}
     */
    public function validar(string $sql): array
    {
        $limpo = $this->removerComentarios($sql);
        $limpo = trim($limpo);

        if (empty($limpo)) {
            return ['permitido' => false, 'motivo' => 'Consulta vazia.'];
        }

        // Deve começar com SELECT
        if (!preg_match('/^SELECT\s/i', $limpo)) {
            return [
                'permitido' => false,
                'motivo'    => 'Apenas consultas SELECT são permitidas neste console.',
            ];
        }

        // Bloqueia palavras-chave perigosas como comandos independentes
        $bloqueados = [
            'EXECUTE', 'INSERT', 'UPDATE', 'DELETE', 'DROP', 'ALTER',
            'CREATE', 'TRUNCATE', 'GRANT', 'REVOKE', 'MERGE', 'CALL',
            'BEGIN', 'COMMIT', 'ROLLBACK', 'SET TRANSACTION',
        ];

        foreach ($bloqueados as $kw) {
            // Regex: keyword como palavra isolada (não dentro de nomes ou strings)
            if (preg_match('/\b' . preg_quote($kw, '/') . '\b/i', $limpo)) {
                return [
                    'permitido' => false,
                    'motivo'    => "Comando \"{$kw}\" não é permitido no SQL Console.",
                ];
            }
        }

        return ['permitido' => true, 'motivo' => ''];
    }

    // -------------------------------------------------------------------------
    // Execução
    // -------------------------------------------------------------------------

    /**
     * Executa o SELECT e retorna resultado estruturado.
     *
     * @return array{
     *   sucesso: bool,
     *   colunas: array,
     *   linhas: array,
     *   total_exibido: int,
     *   truncado: bool,
     *   tempo_ms: float,
     *   erro: string
     * }
     */
    public function executar(string $sql): array
    {
        $validacao = $this->validar($sql);

        if (!$validacao['permitido']) {
            return $this->respostaErro($validacao['motivo']);
        }

        try {
            $pdo   = Database::conectar();
            $inicio = microtime(true);
            $stmt  = $pdo->query($sql);
            $fim   = microtime(true);

            // Busca até LIMITE + 1 para saber se foi truncado
            $linhasBrutas = $stmt->fetchAll(\PDO::FETCH_ASSOC);
            $truncado     = count($linhasBrutas) > self::LIMITE_LINHAS;
            $linhasBrutas = array_slice($linhasBrutas, 0, self::LIMITE_LINHAS);

            // Colunas da query
            $colunas = !empty($linhasBrutas)
                ? array_keys($linhasBrutas[0])
                : $this->colunasDaQuery($stmt);

            // Sanitiza valores (BLOB → string, null → representação legível)
            $linhas = array_map(fn($linha) => $this->sanitizarLinha($linha), $linhasBrutas);

            return [
                'sucesso'       => true,
                'colunas'       => $colunas,
                'linhas'        => $linhas,
                'total_exibido' => count($linhas),
                'truncado'      => $truncado,
                'tempo_ms'      => round(($fim - $inicio) * 1000, 2),
                'erro'          => '',
            ];

        } catch (\PDOException $e) {
            return $this->respostaErro($this->traduzirErroPDO($e->getMessage()));
        }
    }

    // -------------------------------------------------------------------------
    // Internos
    // -------------------------------------------------------------------------

    private function removerComentarios(string $sql): string
    {
        // Remove comentários de linha --
        $sql = preg_replace('/--[^\n]*/m', '', $sql);
        // Remove comentários de bloco /* */
        $sql = preg_replace('/\/\*.*?\*\//s', '', $sql);
        return $sql;
    }

    private function sanitizarLinha(array $linha): array
    {
        $saida = [];
        foreach ($linha as $campo => $valor) {
            if ($valor === null) {
                $saida[$campo] = null;
            } elseif (is_resource($valor)) {
                // BLOB binário
                $saida[$campo] = '[BLOB]';
            } else {
                $saida[$campo] = (string) $valor;
            }
        }
        return $saida;
    }

    private function colunasDaQuery(\PDOStatement $stmt): array
    {
        $colunas = [];
        for ($i = 0; $i < $stmt->columnCount(); $i++) {
            $meta      = $stmt->getColumnMeta($i);
            $colunas[] = $meta['name'] ?? "col{$i}";
        }
        return $colunas;
    }

    private function respostaErro(string $mensagem): array
    {
        return [
            'sucesso'       => false,
            'colunas'       => [],
            'linhas'        => [],
            'total_exibido' => 0,
            'truncado'      => false,
            'tempo_ms'      => 0,
            'erro'          => $mensagem,
        ];
    }

    private function traduzirErroPDO(string $msg): string
    {
        $map = [
            'Column unknown'         => 'Coluna não encontrada.',
            'Table unknown'          => 'Tabela não encontrada.',
            'token unknown'          => 'Erro de sintaxe SQL.',
            'Dynamic SQL Error'      => 'Erro SQL dinâmico.',
            'arithmetic exception'   => 'Erro aritmético (divisão por zero?).',
            'conversion error'       => 'Erro de conversão de tipo.',
        ];
        foreach ($map as $trecho => $texto) {
            if (stripos($msg, $trecho) !== false) return $texto . ' Detalhe: ' . $msg;
        }
        return $msg;
    }
}
