<?php
namespace Security;

use Core\Config;
use Utils\Logger;
use Database\Database;

/**
 * Classe de segurança aprimorada
 * 
 * Versão melhorada que:
 * - Utiliza namespaces para organização
 * - Implementa padrão Singleton de forma mais robusta
 * - Melhora a proteção contra ataques comuns
 * - Implementa validação de entrada mais rigorosa
 */

class Security {
    private static $instance = null;
    private $config;
    private $logger;
    private $db;
    private $token;
    
    /**
     * Construtor privado (padrão Singleton)
     */
    private function __construct() {
        $this->config = Config::getInstance();
        $this->logger = new Logger('security');
        $this->db = Database::getInstance();
        
        // Iniciar sessão se ainda não estiver ativa
        if (session_status() == PHP_SESSION_NONE) {
            session_start();
        }
        
        // Gerar token CSRF se não existir
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        
        $this->token = $_SESSION['csrf_token'];
    }
    
    /**
     * 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 Security
     */
    public static function getInstance() {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        
        return self::$instance;
    }
    
    /**
     * Sanitiza entrada de texto
     * 
     * @param string $input Texto a ser sanitizado
     * @return string Texto sanitizado
     */
    public function sanitizeInput($input) {
        if (is_array($input)) {
            $output = [];
            foreach ($input as $key => $value) {
                $output[$key] = $this->sanitizeInput($value);
            }
            return $output;
        }
        
        // Remover espaços em branco extras
        $output = trim($input);
        
        // Converter caracteres especiais em entidades HTML
        $output = htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
        
        // Escapar para uso seguro em SQL
        $conn = $this->db->getConnection();
        if ($conn instanceof \mysqli) {
            $output = $conn->real_escape_string($output);
        }
        
        return $output;
    }
    
    /**
     * Sanitiza e valida um endereço de e-mail
     * 
     * @param string $email E-mail a ser validado
     * @return string|false E-mail sanitizado ou false se inválido
     */
    public function sanitizeEmail($email) {
        $email = trim($email);
        $email = filter_var($email, FILTER_SANITIZE_EMAIL);
        
        if (filter_var($email, FILTER_VALIDATE_EMAIL)) {
            return $email;
        }
        
        $this->logger->warning("E-mail inválido: $email");
        return false;
    }
    
    /**
     * Sanitiza e valida uma URL
     * 
     * @param string $url URL a ser validada
     * @return string|false URL sanitizada ou false se inválida
     */
    public function sanitizeUrl($url) {
        $url = trim($url);
        $url = filter_var($url, FILTER_SANITIZE_URL);
        
        if (filter_var($url, FILTER_VALIDATE_URL)) {
            return $url;
        }
        
        $this->logger->warning("URL inválida: $url");
        return false;
    }
    
    /**
     * Sanitiza e valida um número inteiro
     * 
     * @param mixed $int Valor a ser validado como inteiro
     * @return int|false Inteiro sanitizado ou false se inválido
     */
    public function sanitizeInt($int) {
        $int = filter_var($int, FILTER_SANITIZE_NUMBER_INT);
        
        if (filter_var($int, FILTER_VALIDATE_INT) !== false) {
            return (int)$int;
        }
        
        $this->logger->warning("Inteiro inválido: $int");
        return false;
    }
    
    /**
     * Gera um campo de token CSRF para formulários
     * 
     * @return string HTML do campo de token CSRF
     */
    public function csrfField() {
        return '<input type="hidden" name="csrf_token" value="' . $this->token . '">';
    }
    
    /**
     * Verifica se o token CSRF é válido
     * 
     * @return bool True se o token for válido, false caso contrário
     */
    public function validateCsrfToken() {
        if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $this->token) {
            $this->logger->warning("Validação CSRF falhou", [
                'received' => $_POST['csrf_token'] ?? 'não fornecido',
                'expected' => $this->token,
                'ip' => $this->getClientIp()
            ]);
            return false;
        }
        
        return true;
    }
    
    /**
     * 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 = '') {
        return $this->db->executeQuery($sql, $params, $types);
    }
    
    /**
     * Gera um hash seguro de senha
     * 
     * @param string $password Senha em texto plano
     * @return string Hash da senha
     */
    public function hashPassword($password) {
        return password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
    }
    
    /**
     * Verifica se uma senha corresponde a um hash
     * 
     * @param string $password Senha em texto plano
     * @param string $hash Hash armazenado
     * @return bool True se a senha corresponder ao hash, false caso contrário
     */
    public function verifyPassword($password, $hash) {
        return password_verify($password, $hash);
    }
    
    /**
     * Gera um token aleatório seguro
     * 
     * @param int $length Comprimento do token
     * @return string Token gerado
     */
    public function generateToken($length = 32) {
        return bin2hex(random_bytes($length / 2));
    }
    
    /**
     * Verifica se uma requisição é AJAX
     * 
     * @return bool True se for uma requisição AJAX, false caso contrário
     */
    public function isAjaxRequest() {
        return (!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && 
                strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
    }
    
    /**
     * Obtém o endereço IP real do visitante
     * 
     * @return string Endereço IP
     */
    public function getClientIp() {
        $ip = '';
        
        // Verificar cabeçalhos comuns para IP real
        $headers = [
            'HTTP_CLIENT_IP',
            'HTTP_X_FORWARDED_FOR',
            'HTTP_X_FORWARDED',
            'HTTP_X_CLUSTER_CLIENT_IP',
            'HTTP_FORWARDED_FOR',
            'HTTP_FORWARDED',
            'REMOTE_ADDR'
        ];
        
        foreach ($headers as $header) {
            if (!empty($_SERVER[$header])) {
                $ips = explode(',', $_SERVER[$header]);
                $ip = trim($ips[0]);
                
                if (filter_var($ip, FILTER_VALIDATE_IP)) {
                    break;
                }
            }
        }
        
        return filter_var($ip, FILTER_VALIDATE_IP) ? $ip : '0.0.0.0';
    }
    
    /**
     * Implementa proteção contra ataques de força bruta
     * 
     * @param string $action Nome da ação (ex: 'login', 'reset_password')
     * @param string $identifier Identificador único (ex: e-mail, nome de usuário)
     * @param int $maxAttempts Número máximo de tentativas
     * @param int $timeWindow Janela de tempo em segundos
     * @return bool True se a ação for permitida, false caso contrário
     */
    public function rateLimiter($action, $identifier, $maxAttempts = 5, $timeWindow = 300) {
        $key = "rate_limit:{$action}:{$identifier}";
        $now = time();
        
        // Verificar se a tabela de rate limiting existe
        $this->createRateLimitTable();
        
        // Limpar registros antigos
        $this->cleanupRateLimits($timeWindow);
        
        // Obter tentativas recentes
        $sql = "SELECT COUNT(*) as attempts FROM rate_limits WHERE action = ? AND identifier = ? AND timestamp > ?";
        $result = $this->executeQuery($sql, [$action, $identifier, $now - $timeWindow], 'ssi');
        
        if ($result && $row = $result->fetch_assoc()) {
            $attempts = (int)$row['attempts'];
            
            if ($attempts >= $maxAttempts) {
                $this->logger->warning("Rate limit excedido", [
                    'action' => $action,
                    'identifier' => $identifier,
                    'attempts' => $attempts,
                    'ip' => $this->getClientIp()
                ]);
                return false;
            }
        }
        
        // Registrar tentativa
        $sql = "INSERT INTO rate_limits (action, identifier, ip, timestamp) VALUES (?, ?, ?, ?)";
        $this->executeQuery($sql, [$action, $identifier, $this->getClientIp(), $now], 'sssi');
        
        return true;
    }
    
    /**
     * Cria a tabela de rate limiting se não existir
     */
    private function createRateLimitTable() {
        $schema = "CREATE TABLE IF NOT EXISTS rate_limits (
            id INT(11) NOT NULL AUTO_INCREMENT,
            action VARCHAR(50) NOT NULL,
            identifier VARCHAR(255) NOT NULL,
            ip VARCHAR(45) NOT NULL,
            timestamp INT(11) NOT NULL,
            PRIMARY KEY (id),
            INDEX (action, identifier),
            INDEX (timestamp)
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
        
        $this->db->createTableIfNotExists('rate_limits', $schema);
    }
    
    /**
     * Limpa registros antigos de rate limiting
     * 
     * @param int $timeWindow Janela de tempo em segundos
     */
    private function cleanupRateLimits($timeWindow) {
        $now = time();
        $sql = "DELETE FROM rate_limits WHERE timestamp < ?";
        $this->executeQuery($sql, [$now - $timeWindow], 'i');
    }
}
