<?php
/**
 * ============================================================
 * CSRF PROTECTION
 * ============================================================
 * Simple token-based CSRF protection for forms and AJAX requests
 */

class CSRF {
    /**
     * Generate CSRF token and store in session
     * @return string
     */
    public static function generateToken(): string {
        if (session_status() === PHP_SESSION_NONE) {
            session_start();
        }
        
        // Generate new token if not exists or expired
        if (empty($_SESSION[CSRF_TOKEN_NAME]) || self::isTokenExpired()) {
            $_SESSION[CSRF_TOKEN_NAME] = bin2hex(random_bytes(32));
            $_SESSION['csrf_token_time'] = time();
        }
        
        return $_SESSION[CSRF_TOKEN_NAME];
    }
    
    /**
     * Get current CSRF token
     * @return string
     */
    public static function getToken(): string {
        if (empty($_SESSION[CSRF_TOKEN_NAME])) {
            return self::generateToken();
        }
        return $_SESSION[CSRF_TOKEN_NAME];
    }
    
    /**
     * Validate CSRF token from request
     * @param string|null $token Token from request
     * @return bool
     */
    public static function validateToken(?string $token): bool {
        if (empty($token) || empty($_SESSION[CSRF_TOKEN_NAME])) {
            return false;
        }
        
        // Use timing-safe comparison
        return hash_equals($_SESSION[CSRF_TOKEN_NAME], $token);
    }
    
    /**
     * Validate token from POST or header (for AJAX)
     * @return bool
     */
    public static function validate(): bool {
        // Check POST data first
        $token = $_POST[CSRF_TOKEN_NAME] ?? null;
        
        // If not in POST, check header (for AJAX)
        if (empty($token)) {
            $token = $_SERVER['HTTP_X_CSRF_TOKEN'] ?? null;
        }
        
        return self::validateToken($token);
    }
    
    /**
     * Check if token has expired (tokens valid for session lifetime)
     * @return bool
     */
    private static function isTokenExpired(): bool {
        if (empty($_SESSION['csrf_token_time'])) {
            return true;
        }
        
        // Token expires after session lifetime
        return (time() - $_SESSION['csrf_token_time']) > SESSION_LIFETIME;
    }
    
    /**
     * Regenerate token (call after successful form submission if needed)
     * @return string
     */
    public static function regenerateToken(): string {
        unset($_SESSION[CSRF_TOKEN_NAME], $_SESSION['csrf_token_time']);
        return self::generateToken();
    }
    
    /**
     * Output hidden input field for forms
     * @return string
     */
    public static function inputField(): string {
        $token = self::getToken();
        $name = CSRF_TOKEN_NAME;
        return "<input type=\"hidden\" name=\"{$name}\" value=\"{$token}\">";
    }
    
    /**
     * Get meta tag for AJAX (to be placed in head)
     * @return string
     */
    public static function metaTag(): string {
        $token = self::getToken();
        return "<meta name=\"csrf-token\" content=\"{$token}\">";
    }
}

/**
 * Shorthand function to get CSRF input field
 * @return string
 */
function csrf_field(): string {
    return CSRF::inputField();
}

/**
 * Shorthand function to get CSRF token
 * @return string
 */
function csrf_token(): string {
    return CSRF::getToken();
}

/**
 * Shorthand function to validate CSRF
 * @return bool
 */
function csrf_validate(): bool {
    return CSRF::validate();
}
