<?php
namespace Database;

use Core\Config;
use Utils\Logger;

/**
 * Classe de conexão com banco de dados
 * 
 * Versão melhorada que:
 * - Utiliza namespaces para organização
 * - Implementa padrão Singleton de forma mais robusta
 * - Melhora o tratamento de erros
 * - Implementa verificação de integridade do banco de dados
 */

class Database {
    private static $instance = null;
    private $conn = null;
    private $config;
    private $logger;
    
    /**
     * Construtor privado (padrão Singleton)
     */
    private function __construct() {
        $this->config = Config::getInstance();
        $this->logger = new Logger('database');
        $this->connect();
    }
    
    /**
     * Previne clonagem do objeto (padrão Singleton)
     */
    private function __clone() {}
    
    /**
     * Previne desserialização do objeto (padrão Singleton)
     */
    private function __wakeup() {}
    
    /**
     * Obtém a instância única da classe
     * 
     * @return Database
     */
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    /**
     * Conecta ao banco de dados
     * 
     * @return bool Sucesso ou falha
     */
    private function connect() {
        // Obter configurações do banco de dados
        $host = $this->config->get('db.host');
        $user = $this->config->get('db.user');
        $pass = $this->config->get('db.pass');
        $name = $this->config->get('db.name');
        $charset = $this->config->get('db.charset', 'utf8mb4');
        $debug_mode = $this->config->get('debug_mode', false);
        
        // Verificar se as configurações estão definidas
        if (empty($host) || empty($user) || empty($name)) {
            $this->logger->error("Configurações de banco de dados incompletas");
            return false;
        }
        
        // Criar conexão com tratamento de erro aprimorado
        try {
            $this->conn = new \mysqli($host, $user, $pass, $name);
            
            // Verificar conexão
            if ($this->conn->connect_error) {
                $this->logger->error("Falha na conexão: " . $this->conn->connect_error);
                
                // Exibir mensagem de erro detalhada em modo de debug, ou amigável em produção
                if ($debug_mode) {
                    throw new \Exception("Erro de conexão com o banco de dados: " . $this->conn->connect_error);
                } else {
                    throw new \Exception("Não foi possível conectar ao banco de dados. Por favor, tente novamente mais tarde.");
                }
            }
            
            // Definir charset
            $this->conn->set_charset($charset);
            
            return true;
        } catch (\Exception $e) {
            $this->logger->error("Exceção na conexão: " . $e->getMessage());
            
            // Exibir mensagem de erro detalhada em modo de debug, ou amigável em produção
            if ($debug_mode) {
                throw new \Exception("Exceção ao conectar ao banco de dados: " . $e->getMessage());
            } else {
                throw new \Exception("Ocorreu um erro ao conectar ao banco de dados. Por favor, tente novamente mais tarde.");
            }
        }
    }
    
    /**
     * Obtém a conexão com o banco de dados
     * 
     * @return mysqli Objeto de conexão
     */
    public function getConnection() {
        // Verificar se a conexão está ativa
        if ($this->conn === null || !$this->conn->ping()) {
            $this->connect();
        }
        
        return $this->conn;
    }
    
    /**
     * Executa uma consulta SQL com prepared statements
     * 
     * @param string $sql Consulta SQL com placeholders
     * @param array $params Parâmetros para a consulta
     * @param string $types Tipos dos parâmetros (i: int, d: double, s: string, b: blob)
     * @return mysqli_result|bool Resultado da consulta ou false em caso de erro
     */
    public function executeQuery($sql, $params = [], $types = '') {
        if ($this->conn === null) {
            $this->connect();
        }
        
        if (empty($params)) {
            return $this->conn->query($sql);
        }
        
        $stmt = $this->conn->prepare($sql);
        if (!$stmt) {
            $this->logger->error("Erro ao preparar consulta: " . $this->conn->error);
            return false;
        }
        
        if (!empty($params)) {
            if (empty($types)) {
                // Determinar tipos automaticamente
                $types = '';
                foreach ($params as $param) {
                    if (is_int($param)) {
                        $types .= 'i';
                    } elseif (is_float($param)) {
                        $types .= 'd';
                    } elseif (is_string($param)) {
                        $types .= 's';
                    } else {
                        $types .= 'b';
                    }
                }
            }
            
            $stmt->bind_param($types, ...$params);
        }
        
        $result = $stmt->execute();
        
        if (!$result) {
            $this->logger->error("Erro ao executar consulta: " . $stmt->error);
            $stmt->close();
            return false;
        }
        
        $result = $stmt->get_result();
        $stmt->close();
        
        return $result;
    }
    
    /**
     * Verifica se uma tabela existe
     * 
     * @param string $table Nome da tabela
     * @return bool True se a tabela existir, false caso contrário
     */
    public function tableExists($table) {
        $result = $this->executeQuery("SHOW TABLES LIKE ?", [$table], 's');
        return $result && $result->num_rows > 0;
    }
    
    /**
     * Verifica se uma coluna existe em uma tabela
     * 
     * @param string $table Nome da tabela
     * @param string $column Nome da coluna
     * @return bool True se a coluna existir, false caso contrário
     */
    public function columnExists($table, $column) {
        if (!$this->tableExists($table)) {
            return false;
        }
        
        $result = $this->executeQuery("SHOW COLUMNS FROM {$table} LIKE ?", [$column], 's');
        return $result && $result->num_rows > 0;
    }
    
    /**
     * Cria uma tabela se não existir
     * 
     * @param string $table Nome da tabela
     * @param string $schema Esquema da tabela (SQL CREATE TABLE)
     * @return bool True se a tabela foi criada ou já existia, false em caso de erro
     */
    public function createTableIfNotExists($table, $schema) {
        if ($this->tableExists($table)) {
            return true;
        }
        
        $result = $this->conn->query($schema);
        
        if ($result) {
            $this->logger->info("Tabela {$table} criada com sucesso");
            return true;
        } else {
            $this->logger->error("Erro ao criar tabela {$table}: " . $this->conn->error);
            return false;
        }
    }
    
    /**
     * Adiciona uma coluna a uma tabela se não existir
     * 
     * @param string $table Nome da tabela
     * @param string $column Nome da coluna
     * @param string $definition Definição da coluna (ex: "VARCHAR(255) NOT NULL")
     * @return bool True se a coluna foi adicionada ou já existia, false em caso de erro
     */
    public function addColumnIfNotExists($table, $column, $definition) {
        if (!$this->tableExists($table)) {
            $this->logger->error("Tabela {$table} não existe");
            return false;
        }
        
        if ($this->columnExists($table, $column)) {
            return true;
        }
        
        $sql = "ALTER TABLE {$table} ADD COLUMN {$column} {$definition}";
        $result = $this->conn->query($sql);
        
        if ($result) {
            $this->logger->info("Coluna {$column} adicionada à tabela {$table}");
            return true;
        } else {
            $this->logger->error("Erro ao adicionar coluna {$column} à tabela {$table}: " . $this->conn->error);
            return false;
        }
    }
    
    /**
     * Verifica se um registro existe em uma tabela
     * 
     * @param string $table Nome da tabela
     * @param string $column Nome da coluna
     * @param mixed $value Valor a ser verificado
     * @return bool True se o registro existir, false caso contrário
     */
    public function recordExists($table, $column, $value) {
        if (!$this->tableExists($table)) {
            return false;
        }
        
        $sql = "SELECT 1 FROM {$table} WHERE {$column} = ? LIMIT 1";
        $result = $this->executeQuery($sql, [$value], is_int($value) ? 'i' : 's');
        
        return $result && $result->num_rows > 0;
    }
    
    /**
     * Fecha a conexão com o banco de dados
     */
    public function close() {
        if ($this->conn !== null) {
            $this->conn->close();
            $this->conn = null;
        }
    }
    
    /**
     * Destrutor
     */
    public function __destruct() {
        $this->close();
    }
}
