<?php
// Depto de Engenharia e Automação - Master Automação - Autor: Derlivon Silva

namespace App\Services;

/**
 * Gerencia e testa a conexão com o banco Firebird via PDO.
 */
class FirebirdService
{
    // -------------------------------------------------------------------------
    // Health-check de host remoto (fail-fast pra loja offline)
    // -------------------------------------------------------------------------

    /**
     * Testa se o servidor Firebird está alcançável via TCP (fail-fast).
     * Sem isso, o PDO tenta conectar e fica ~60s travado quando o servidor está
     * desligado (loja com expediente, host caído, VPN fora).
     *
     * Cache em arquivo com TTL curto (30s) — assim navegar o BI não dispara um
     * probe a cada request, mas o status atualiza rápido quando a loja religar.
     *
     * @return bool true = host respondeu na porta; false = offline/desligado/sem rota
     */
    public static function pingHost(string $host, int $port, int $timeoutMs = 250): bool
    {
        // Host vazio ou local → não tem o que pingar (modo single-loja, banco local)
        if ($host === '' || strtolower($host) === 'localhost' || $host === '127.0.0.1') {
            return true;
        }

        // Cache em memória (mesma request)
        static $mem = [];
        $key = $host . ':' . $port;
        if (isset($mem[$key])) return $mem[$key];

        // Cache em arquivo (entre requests, TTL 30s)
        $cacheDir  = dirname(__DIR__, 2) . '/storage/cache';
        $cacheFile = $cacheDir . '/loja_ping_' . md5($key) . '.json';
        if (is_file($cacheFile) && (time() - filemtime($cacheFile)) < 30) {
            $j = @json_decode((string)@file_get_contents($cacheFile), true);
            if (is_array($j) && isset($j['ok'])) {
                return $mem[$key] = (bool) $j['ok'];
            }
        }

        // Probe TCP
        $timeoutSec = max(0.1, $timeoutMs / 1000);
        $errno = 0; $errstr = '';
        $sock = @fsockopen($host, $port, $errno, $errstr, $timeoutSec);
        $ok = ($sock !== false);
        if ($sock !== false) @fclose($sock);

        // Grava cache
        if (!is_dir($cacheDir)) @mkdir($cacheDir, 0777, true);
        @file_put_contents($cacheFile, json_encode(['ok' => $ok, 't' => time()]));

        return $mem[$key] = $ok;
    }

    // -------------------------------------------------------------------------
    // Teste de conexão
    // -------------------------------------------------------------------------

    /**
     * Tenta conectar ao Firebird e retorna um relatório detalhado.
     *
     * @return array{success: bool, message: string, detalhes: string, tabelas: int}
     */
    public function testarConexao(array $config): array
    {
        // Verifica se a extensão PDO Firebird está disponível
        if (!extension_loaded('pdo_firebird')) {
            return [
                'success'  => false,
                'message'  => 'Extensão pdo_firebird não está habilitada no PHP.',
                'detalhes' => 'Ative "extension=pdo_firebird" no php.ini e reinicie o servidor. '
                            . 'O cliente Firebird também precisa estar instalado na máquina.',
                'tabelas'  => 0,
            ];
        }

        try {
            $pdo = $this->criarConexao($config);

            // Conta as tabelas do usuário no banco
            $stmt = $pdo->query(
                "SELECT COUNT(*) FROM RDB\$RELATIONS WHERE RDB\$SYSTEM_FLAG = 0 AND RDB\$VIEW_BLR IS NULL"
            );
            $totalTabelas = (int) $stmt->fetchColumn();

            return [
                'success'  => true,
                'message'  => 'Conexão estabelecida com sucesso!',
                'detalhes' => "Banco conectado. {$totalTabelas} tabela(s) de usuário encontrada(s).",
                'tabelas'  => $totalTabelas,
            ];

        } catch (\PDOException $e) {
            return [
                'success'  => false,
                'message'  => 'Falha na conexão com o Firebird.',
                'detalhes' => $this->traduzirErro($e->getMessage()),
                'tabelas'  => 0,
            ];
        }
    }

    // -------------------------------------------------------------------------
    // Criação da conexão PDO
    // -------------------------------------------------------------------------

    /**
     * Cria e retorna uma instância PDO para o Firebird.
     */
    public function criarConexao(array $config): \PDO
    {
        $host     = $config['host']     ?? 'localhost';
        $porta    = $config['port']     ?? 3050;
        $database = $config['database'] ?? '';
        $charset  = $config['charset']  ?? 'UTF8';
        $tipo     = $config['tipo']     ?? 'remota';

        if ($tipo === 'local' || $host === '' || strtolower($host) === 'localhost' || $host === '127.0.0.1') {
            // Local: caminho direto (mais confiável)
            $dsn = "firebird:dbname={$database};charset={$charset}";
        } else {
            // Remoto: fail-fast se o servidor da loja estiver desligado/offline.
            // Sem isso, o PDO Firebird trava ~60s tentando conectar (cliente fecha
            // o bar à noite → BI inteiro pendurado).
            if (!self::pingHost($host, (int)$porta)) {
                $nome = $config['empresa'] ?? $host;
                throw new \RuntimeException(
                    "Loja \"{$nome}\" está OFFLINE — o servidor da loja está desligado "
                    . "ou sem rede. Religue o servidor da loja e tente novamente."
                );
            }
            // Remoto: formato "host/porta:caminho" — funciona com caminho Windows remoto.
            // (o formato host=;port=;dbname=C:\... falha com "I/O error CreateFile" em banco remoto)
            $dsn = "firebird:dbname={$host}/{$porta}:{$database};charset={$charset}";
        }

        try {
            return new \PDO(
                $dsn,
                $config['username'] ?? 'SYSDBA',
                $config['password'] ?? '',
                [
                    \PDO::ATTR_ERRMODE            => \PDO::ERRMODE_EXCEPTION,
                    \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
                    \PDO::ATTR_TIMEOUT            => 10,
                    // ATTR_PERSISTENT removido: conexões persistentes não são recriadas
                    // quando o Firebird reinicia (PHP devolve a mesma conexão morta do pool
                    // mesmo com new PDO), causando erro -901. Com cache PHP nas queries,
                    // o overhead de abrir nova conexão por request é aceitável.
                ]
            );
        } catch (\PDOException $e) {
            // Erro amigável (ex.: loja remota offline / VPN caída) em vez do SQLSTATE cru.
            $nome = $config['empresa'] ?? ($host !== '' ? $host : 'banco de dados');
            $amigavel = $this->traduzirErro($e->getMessage());
            if (stripos($e->getMessage(), 'network request') !== false) {
                $amigavel = 'sem conexão com a loja (verifique a VPN/rede com ' . $host . ').';
            }
            throw new \RuntimeException("Loja \"{$nome}\" indisponível — {$amigavel}", 0, $e);
        }
    }

    // -------------------------------------------------------------------------
    // Utilitários
    // -------------------------------------------------------------------------

    /**
     * Traduz mensagens de erro comuns do Firebird para português.
     */
    private function traduzirErro(string $mensagem): string
    {
        $traducoes = [
            'Unable to complete network request'    => 'Não foi possível alcançar o servidor. Verifique o host e a porta.',
            'connection rejected by remote interface'=> 'Conexão recusada pelo servidor Firebird.',
            'Your user name and password are not defined' => 'Usuário ou senha incorretos.',
            'I/O error during "open" operation'     => 'Arquivo .FDB não encontrado no caminho informado.',
            'File is not a valid database'          => 'O arquivo informado não é um banco Firebird válido.',
            'unavailable database'                  => 'Banco de dados indisponível.',
        ];

        foreach ($traducoes as $trecho => $texto) {
            if (stripos($mensagem, $trecho) !== false) {
                return $texto;
            }
        }

        return $mensagem;
    }
}
