<?php
/**
 * Security Middleware for Ton Sui Mining
 * 
 * Applies security headers, CSRF protection, and session validation to all requests
 */

class SecurityMiddleware {
    // CSRF token name
    private const CSRF_TOKEN_NAME = 'csrf_token';
    
    // CSRF token lifetime in seconds (30 minutes)
    private const CSRF_TOKEN_LIFETIME = 1800;
    /**
     * Apply security headers to the response
     */
    public static function applySecurityHeaders() {
        // Remove potentially dangerous headers
        header_remove('X-Powered-By');
        header_remove('Server');
        
        // Security headers
        header('X-Content-Type-Options: nosniff');
        header('X-Frame-Options: DENY');
        header('X-XSS-Protection: 1; mode=block');
        header('Referrer-Policy: same-origin');
        header('Content-Security-Policy: default-src \'self\'');
        header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
        
        // Prevent caching of sensitive pages
        header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
        header('Pragma: no-cache');
    }
    
    /**
     * Generate a new CSRF token
     * 
     * @return string The generated CSRF token
     */
    public static function generateCsrfToken() {
        if (empty($_SESSION[self::CSRF_TOKEN_NAME])) {
            $_SESSION[self::CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
            $_SESSION[self::CSRF_TOKEN_NAME . '_time'] = time();
        } elseif (time() - $_SESSION[self::CSRF_TOKEN_NAME . '_time'] > self::CSRF_TOKEN_LIFETIME) {
            // Regenerate token if expired
            $_SESSION[self::CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
            $_SESSION[self::CSRF_TOKEN_NAME . '_time'] = time();
        }
        
        return $_SESSION[self::CSRF_TOKEN_NAME];
    }
    
    /**
     * Validate a CSRF token
     * 
     * @param string $token The token to validate
     * @return bool True if valid, false otherwise
     */
    public static function validateCsrfToken($token) {
        if (empty($_SESSION[self::CSRF_TOKEN_NAME]) || empty($token)) {
            return false;
        }
        
        // Check if token is expired
        if (time() - $_SESSION[self::CSRF_TOKEN_NAME . '_time'] > self::CSRF_TOKEN_LIFETIME) {
            self::regenerateCsrfToken();
            return false;
        }
        
        // Use hash_equals for timing attack prevention
        return hash_equals($_SESSION[self::CSRF_TOKEN_NAME], $token);
    }
    
    /**
     * Regenerate CSRF token
     */
    public static function regenerateCsrfToken() {
        unset($_SESSION[self::CSRF_TOKEN_NAME]);
        unset($_SESSION[self::CSRF_TOKEN_NAME . '_time']);
        return self::generateCsrfToken();
    }
    
    /**
     * Validate the user's session
     * 
     * @param bool $requireAdmin Whether admin access is required
     * @param bool $checkCsrf Whether to check CSRF token (default: true for POST/PUT/DELETE)
     * @return array User data if valid, false otherwise
     */
    public static function validateSession($requireAdmin = false, $checkCsrf = null) {
        // Start session if not already started
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        
        // Check CSRF for non-GET/HEAD/OPTIONS requests by default
        if ($checkCsrf === null) {
            $checkCsrf = !in_array($_SERVER['REQUEST_METHOD'], ['GET', 'HEAD', 'OPTIONS']);
        }
        
        if ($checkCsrf && !self::validateCsrfToken($_POST['_csrf'] ?? $_SERVER['HTTP_X_CSRF_TOKEN'] ?? '')) {
            http_response_code(403);
            echo json_encode(['success' => false, 'message' => 'Invalid CSRF token']);
            exit;
        }
        
        // Check if user is logged in
        if (empty($_SESSION['user_id'])) {
            http_response_code(401);
            echo json_encode(['success' => false, 'message' => 'Authentication required']);
            exit;
        }
        
        // Check for session fixation/hijacking
        if (!self::validateSessionSecurity()) {
            self::destroySession();
            http_response_code(401);
            echo json_encode(['success' => false, 'message' => 'Invalid session']);
            exit;
        }
        
        // Check admin access if required
        if ($requireAdmin && empty($_SESSION['admin_logged_in'])) {
            http_response_code(403);
            echo json_encode(['success' => false, 'message' => 'Admin access required']);
            exit;
        }
        
        // Return user data
        return [
            'id' => $_SESSION['user_id'],
            'username' => $_SESSION['username'] ?? '',
            'email' => $_SESSION['email'] ?? '',
            'is_admin' => !empty($_SESSION['admin_logged_in'])
        ];
    }
    
    /**
     * Validate session security (prevent fixation/hijacking)
     */
    private static function validateSessionSecurity() {
        // Check if user agent has changed
        if (isset($_SESSION['user_agent']) && 
            $_SESSION['user_agent'] !== ($_SERVER['HTTP_USER_AGENT'] ?? '')) {
            return false;
        }
        
        // Check if IP has changed (be careful with this in environments with load balancers)
        $ipKey = 'REMOTE_ADDR';
        if (isset($_SESSION['ip_fingerprint'])) {
            $currentFingerprint = md5(($_SERVER[$ipKey] ?? '') . ($_SERVER['HTTP_USER_AGENT'] ?? ''));
            if ($_SESSION['ip_fingerprint'] !== $currentFingerprint) {
                return false;
            }
        }
        
        // Check session timeout (30 minutes)
        $timeout = 1800; // 30 minutes in seconds
        if (isset($_SESSION['last_activity']) && (time() - $_SESSION['last_activity'] > $timeout)) {
            return false;
        }
        
        // Update last activity time
        $_SESSION['last_activity'] = time();
        
        return true;
    }
    
    /**
     * Completely destroy the session
     */
    public static function destroySession() {
        // Clear session data
        $_SESSION = [];
        
        // Delete the session cookie
        if (ini_get('session.use_cookies')) {
            $params = session_get_cookie_params();
            setcookie(
                session_name(),
                '',
                [
                    'expires' => time() - 42000,
                    'path' => $params['path'],
                    'domain' => $params['domain'],
                    'secure' => $params['secure'],
                    'httponly' => true,
                    'samesite' => 'Strict'
                ]
            );
        }
        
        // Destroy the session
        if (session_status() === PHP_SESSION_ACTIVE) {
            session_destroy();
        }
    }
    
    /**
     * Log a security event
     * 
     * @param string $event Event type/name
     * @param int|null $userId User ID if applicable
     * @param array|null $details Additional event details
     * @param string|null $ip IP address (defaults to REMOTE_ADDR)
     * @param string|null $userAgent User agent (defaults to HTTP_USER_AGENT)
     * @return bool True if logged successfully, false otherwise
     */
    public static function logSecurityEvent($event, $userId = null, $details = null, $ip = null, $userAgent = null) {
        global $pdo;
        
        $ip = $ip ?? ($_SERVER['REMOTE_ADDR'] ?? 'unknown');
        $userAgent = $userAgent ?? ($_SERVER['HTTP_USER_AGENT'] ?? 'unknown');
        
        if (!isset($pdo)) {
            error_log(sprintf(
                'Security Event (no DB): %s, User: %s, IP: %s, Agent: %s, Details: %s',
                $event,
                $userId ?? 'null',
                $ip,
                $userAgent,
                $details ? json_encode($details) : '{}'
            ));
            return false;
        }
        
        try {
            // Create security_logs table if it doesn't exist
            $pdo->exec("CREATE TABLE IF NOT EXISTS `security_logs` (
                `id` int(11) NOT NULL AUTO_INCREMENT,
                `event_type` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
                `user_id` int(11) DEFAULT NULL,
                `ip_address` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
                `user_agent` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
                `details` text COLLATE utf8mb4_unicode_ci DEFAULT NULL,
                `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
                PRIMARY KEY (`id`),
                KEY `idx_event_type` (`event_type`),
                KEY `idx_user_id` (`user_id`),
                KEY `idx_created_at` (`created_at`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");
            
            $stmt = $pdo->prepare('INSERT INTO `security_logs` (`event_type`, `user_id`, `ip_address`, `user_agent`, `details`) 
                                 VALUES (?, ?, ?, ?, ?)');
            $stmt->execute([
                $event,
                $userId,
                $ip,
                $userAgent,
                $details ? json_encode($details) : null
            ]);
            return true;
        } catch (Exception $e) {
            error_log(sprintf(
                'Failed to log security event: %s, Error: %s',
                $event,
                $e->getMessage()
            ));
            return false;
        }
    }

    /**
     * Get the current CSRF token (delegates to CsrfMiddleware)
     * 
     * @return string The current CSRF token
     */
    public static function getCsrfToken() {
        require_once __DIR__ . '/CsrfMiddleware.php';
        return CsrfMiddleware::getCsrfToken();
    }
    
    /**
     * Get CSRF token meta tag for HTML forms (delegates to CsrfMiddleware)
     * 
     * @return string HTML meta tag with CSRF token
     */
    public static function getCsrfMetaTag() {
        require_once __DIR__ . '/CsrfMiddleware.php';
        return CsrfMiddleware::getCsrfMetaTag();
    }
    
    /**
     * Get CSRF token as a hidden input field for forms (delegates to CsrfMiddleware)
     * 
     * @return string HTML input field with CSRF token
     */
    public static function getCsrfField() {
        require_once __DIR__ . '/CsrfMiddleware.php';
        return CsrfMiddleware::getCsrfField();
    }
}

// Apply security headers to all requests
SecurityMiddleware::applySecurityHeaders();
