<?php
/**
 * @file qtrClientId.php
 * @brief Client identification utilities for security modules
 *
 * WHAT IT DOES (high-level)
 *  - Provides client fingerprinting and hashing utilities
 *  - Delegates IP resolution and validation to CQtrIPManager
 *  - Normalizes User-Agent headers for consistent identification
 *  - Creates stable client keys for tracking and rate limiting
 *
 * FEATURES
 *  - Client key generation: IP + UA hash combined into single identifier
 *  - User-Agent normalization: trim whitespace, cap length
 *  - Stable hashing: SHA256 for collision resistance
 *  - IP validation and resolution: delegated to CQtrIPManager (centralized)
 *  - Trusted proxy support: delegated to CQtrIPManager
 *
 * USAGE
 *  - Get real client IP: Use CQtrClientId::ResolveClientIP() instead
 *  - Validate IP: Use CQtrClientId::IsValidIP() instead
 *  - Create client key: $key = CQtrClientId::GetClientKey()
 *  - Normalize UA: $ua = CQtrClientId::NormalizeUserAgent($raw_ua)
 *
 *
 * @author Quttera
 */

if (!defined('ABSPATH')) exit;

require_once('qtrIPManager.php');

final class CQtrClientId
{
    /**
     * GetClientKey()
     * -------
     * Generate a stable client key for rate limiting and tracking.
     *
     * Combines resolved client IP and User-Agent into a single fingerprint.
     * Different User-Agents from the same IP are tracked separately, allowing
     * per-client rate limiting even from shared networks.
     *
     * Use cases:
     *  - Rate limiter lane tracking (CQtrRateLimiter::consume)
     *  - Audit log identification (audit events, decision tracking)
     *  - Client-specific counters (failure counts, block history)
     *  - Session fingerprinting (anti-session-hijacking)
     *
     * Process:
     *  1. Resolve real client IP via CQtrIPManager::ResolveClientIP()
     *  2. Normalize User-Agent from $_SERVER['HTTP_USER_AGENT']
     *  3. Hash normalized UA to 16-char fingerprint
     *  4. Combine IP + UA hash and hash again for stability
     *  5. Return first 32 characters (tradeoff: speed vs collision resistance)
     *
     * IMPORTANT
     *  - Hash is deterministic: same IP+UA always produces same key
     *  - Hash is opaque: cannot reverse-engineer IP from key
     *  - Hash changes if IP or UA changes
     *  - Empty UA still produces valid key (treated as empty string)
     *
     * Example:
     *  $key = CQtrClientId::GetClientKey();
     *  if (!CQtrRateLimiter::consume('rest', $key, $config)) {
     *      wp_die('Rate limited');
     *  }
     *
     * @return string Client key hash (32 hexadecimal characters)
     */
    public static function GetClientKey(): string
    {
        $ip = CQtrIPManager::ResolveClientIP();
        $ua = self::NormalizeUserAgent($_SERVER['HTTP_USER_AGENT'] ?? '');
        $ua_hash = substr(hash('sha256', $ua), 0, 16);
        return self::Hash($ip . ':' . $ua_hash);
    }

    /**
     * NormalizeUserAgent()
     * -------------------
     * Normalize a User-Agent string for consistent identification.
     *
     * Normalizations applied:
     *  1. Convert null/false to empty string (safe for hashing)
     *  2. Trim leading/trailing whitespace
     *  3. Collapse multiple consecutive spaces to single space
     *  4. Cap at 255 characters (storage/performance limits)
     *
     * Why normalize?
     *  - Browsers may include trailing spaces or extra whitespace
     *  - Bots may send malformed or padded User-Agent strings
     *  - Normalization ensures consistent hashing and comparison
     *  - Prevents DoS via extremely long User-Agent strings
     *
     * Use cases:
     *  - Pre-process User-Agent before hashing (in GetClientKey)
     *  - Logging and audit trails (consistent format)
     *  - User-Agent pattern matching (good bot detection)
     *  - Storage constraints (database fields, cache keys)
     *
     * Example:
     *  $raw = "  Mozilla/5.0  (Windows NT 10.0)  ";
     *  $normalized = CQtrClientId::NormalizeUserAgent($raw);
     *  // Result: "Mozilla/5.0 (Windows NT 10.0)"
     *
     * @param string|null $ua Raw User-Agent string (may be null or empty)
     * @return string Normalized User-Agent (max 255 chars, safe for storage)
     */
    public static function NormalizeUserAgent(?string $ua): string
    {
        $ua = (string)$ua;
        $ua = trim($ua);
        $ua = (string)preg_replace('/\s+/', ' ', $ua);
        return substr($ua, 0, 255);
    }

    /**
     * Hash()
     * ----
     * Create a stable short hash of a string.
     *
     * Uses SHA256 algorithm truncated to 32 hexadecimal characters.
     * Provides good collision resistance for non-cryptographic use cases
     * (caching, identification, fingerprinting).
     *
     * Why 32 characters?
     *  - Full SHA256 is 64 hex chars (256 bits)
     *  - Truncated to 32 chars (128 bits) saves storage while maintaining low collision risk
     *  - Still exceeds UUID v4 uniqueness (122 bits)
     *  - Suitable for cache keys, audit logs, rate limit tracking
     *
     * Important limitations:
     *  - NOT cryptographically secure (not for password hashing, security tokens)
     *  - Truncation increases collision probability (acceptable for identifiers)
     *  - Deterministic: same input always produces same hash
     *  - Opaque: cannot reverse-engineer original string from hash
     *
     * Use cases:
     *  - Hashing User-Agent strings (in GetClientKey)
     *  - Creating client fingerprints
     *  - Cache key generation
     *  - Audit log field identifiers
     *  - Rate limit bucket keys
     *
     * Example:
     *  $ua_hash = CQtrClientId::Hash('Mozilla/5.0');
     *  // Always returns same value for same input
     *
     * @param string $s String to hash (any content, any length)
     * @return string Hash (32 hexadecimal characters, lowercase)
     */
    public static function Hash(string $s): string
    {
        return substr(hash('sha256', $s), 0, 32);
    }

    /**
     * ResolveRealIP()
     * ---------------
     * Get the real client IP address, accounting for trusted proxies.
     *
     * @return string Real client IP address
     */
    public static function ResolveRealIP(): string
    {
        return CQtrIPManager::ResolveClientIP();
    }

    /**
     * IsValidIP()
     * -----------
     * Validate if a string is a valid IPv4 or IPv6 address.
     * 
     * @param string $ip IP address to validate
     * @return bool True if valid IPv4 or IPv6
     */
    public static function IsValidIP(string $ip): bool
    {
        return CQtrIPManager::IsValidIP($ip);
    }

    /**
     * IPInCIDR()
     * ----------
     * Check if an IP address is within a given CIDR range.
     * 
     * Example:
     *  CQtrClientId::IPInCIDR('192.168.1.10', '192.168.1.0/24')  // true
     *  CQtrClientId::IPInCIDR('10.0.0.5', '192.168.0.0/16')     // false
     *
     * @deprecated 2.0.0 Use CQtrIPManager::IPInCIDR() instead
     * @param string $ip IP address to check
     * @param string $cidr CIDR range (e.g., "192.168.1.0/24" or "2001:db8::/32")
     * @return bool True if IP is within CIDR range
     */
    public static function IPInCIDR(string $ip, string $cidr): bool
    {
        return CQtrIPManager::IPInCIDR($ip, $cidr);
    }
}

