<?php
/**
 * JwtManager - Gestion sécurisée des tokens JWT
 * Implémentation légère sans dépendances externes
 */
class JwtManager {
    
    private static $secret;
    private static $algorithm = 'HS256';
    private static $defaultExpiration = 3600; // 1 heure
    
    /**
     * Initialiser avec la clé secrète
     */
    private static function init() {
        if (self::$secret === null) {
            $config = DatabaseConfig::getSecurityConfig();
            self::$secret = $config['jwt_secret'];
            self::$defaultExpiration = $config['session_timeout'];
            
            if (!self::$secret) {
                throw new Exception('JWT_SECRET manquant dans la configuration');
            }
            
            if (strlen(self::$secret) < 32) {
                throw new Exception('JWT_SECRET trop court (minimum 32 caractères)');
            }
        }
    }
    
    /**
     * Encoder en Base64 URL-safe
     */
    private static function base64UrlEncode($data) {
        return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
    }
    
    /**
     * Décoder Base64 URL-safe
     */
    private static function base64UrlDecode($data) {
        return base64_decode(str_pad(strtr($data, '-_', '+/'), strlen($data) % 4, '=', STR_PAD_RIGHT));
    }
    
    /**
     * Créer un token JWT
     */
    public static function createToken($payload, $expirationTime = null) {
        self::init();
        
        $expirationTime = $expirationTime ?: time() + self::$defaultExpiration;
        
        // Header JWT
        $header = [
            'typ' => 'JWT',
            'alg' => self::$algorithm
        ];
        
        // Payload avec claims standards
        $claims = array_merge($payload, [
            'iat' => time(),           // Issued at
            'exp' => $expirationTime,  // Expiration
            'iss' => 'KOMBAR_FERD_API', // Issuer
            'jti' => uniqid('', true)  // JWT ID unique
        ]);
        
        // Encoder les parties
        $encodedHeader = self::base64UrlEncode(json_encode($header));
        $encodedPayload = self::base64UrlEncode(json_encode($claims));
        
        // Créer la signature
        $signature = hash_hmac('sha256', $encodedHeader . '.' . $encodedPayload, self::$secret, true);
        $encodedSignature = self::base64UrlEncode($signature);
        
        return $encodedHeader . '.' . $encodedPayload . '.' . $encodedSignature;
    }
    
    /**
     * Vérifier et décoder un token JWT
     */
    public static function verifyToken($token) {
        self::init();
        
        if (!$token) {
            throw new Exception('Token manquant');
        }
        
        $parts = explode('.', $token);
        if (count($parts) !== 3) {
            throw new Exception('Format de token invalide');
        }
        
        list($encodedHeader, $encodedPayload, $encodedSignature) = $parts;
        
        // Vérifier la signature
        $expectedSignature = hash_hmac('sha256', $encodedHeader . '.' . $encodedPayload, self::$secret, true);
        $expectedEncodedSignature = self::base64UrlEncode($expectedSignature);
        
        if (!hash_equals($expectedEncodedSignature, $encodedSignature)) {
            throw new Exception('Signature de token invalide');
        }
        
        // Décoder le payload
        $payload = json_decode(self::base64UrlDecode($encodedPayload), true);
        if (!$payload) {
            throw new Exception('Payload de token invalide');
        }
        
        // Vérifier l'expiration
        if (isset($payload['exp']) && time() > $payload['exp']) {
            throw new Exception('Token expiré');
        }
        
        // Vérifier l'émetteur
        if (isset($payload['iss']) && $payload['iss'] !== 'KOMBAR_FERD_API') {
            throw new Exception('Émetteur de token invalide');
        }
        
        return $payload;
    }
    
    /**
     * Extraire le token du header Authorization
     */
    public static function extractTokenFromHeader() {
        $headers = [];
        
        // Méthode 1: getallheaders() si disponible
        if (function_exists('getallheaders') && getallheaders()) {
            $headers = getallheaders();
        } else {
            // Méthode 2: Parser $_SERVER
            foreach ($_SERVER as $key => $value) {
                if (strpos($key, 'HTTP_') === 0) {
                    $header = str_replace('_', '-', substr($key, 5));
                    $headers[$header] = $value;
                }
            }
        }
        
        $authHeader = null;
        foreach ($headers as $key => $value) {
            if (strtolower($key) === 'authorization') {
                $authHeader = $value;
                break;
            }
        }
        
        if (!$authHeader) {
            return null;
        }
        
        if (preg_match('/Bearer\s+(.*)$/i', $authHeader, $matches)) {
            return $matches[1];
        }
        
        return null;
    }
    
    /**
     * Middleware de vérification d'authentification
     */
    public static function requireAuth() {
        try {
            $token = self::extractTokenFromHeader();
            if (!$token) {
                Utils::errorResponse('Token d\'authentification requis', 401);
            }
            
            $payload = self::verifyToken($token);
            return $payload;
            
        } catch (Exception $e) {
            Utils::errorResponse('Token invalide: ' . $e->getMessage(), 401);
        }
    }
    
    /**
     * Vérifier si un token est proche de l'expiration (pour renouvellement)
     */
    public static function isTokenNearExpiration($token, $thresholdSeconds = 300) {
        try {
            $payload = self::verifyToken($token);
            $exp = $payload['exp'] ?? 0;
            return ($exp - time()) < $thresholdSeconds;
        } catch (Exception $e) {
            return true; // Si on ne peut pas vérifier, considérer comme expiré
        }
    }
    
    /**
     * Générer un token de rafraîchissement (plus longue durée)
     */
    public static function createRefreshToken($payload) {
        return self::createToken(
            array_merge($payload, ['type' => 'refresh']),
            time() + (7 * 24 * 3600) // 7 jours
        );
    }
    
    /**
     * Invalider un token (blacklist basique en base)
     */
    public static function blacklistToken($token) {
        try {
            $payload = self::verifyToken($token);
            $jti = $payload['jti'] ?? null;
            
            if ($jti) {
                $pdo = DatabaseConfig::getConnection();
                $sql = "INSERT INTO token_blacklist (jti, created_at) VALUES (?, NOW()) 
                       ON DUPLICATE KEY UPDATE created_at = NOW()";
                $stmt = $pdo->prepare($sql);
                $stmt->execute([$jti]);
            }
        } catch (Exception $e) {
            // Log l'erreur mais ne pas faire échouer
            Utils::logError("Erreur blacklist token: " . $e->getMessage());
        }
    }
    
    /**
     * Vérifier si un token est en blacklist
     */
    public static function isTokenBlacklisted($token) {
        try {
            $payload = self::verifyToken($token);
            $jti = $payload['jti'] ?? null;
            
            if (!$jti) return false;
            
            $pdo = DatabaseConfig::getConnection();
            $sql = "SELECT COUNT(*) FROM token_blacklist WHERE jti = ?";
            $stmt = $pdo->prepare($sql);
            $stmt->execute([$jti]);
            
            return $stmt->fetchColumn() > 0;
            
        } catch (Exception $e) {
            return false; // En cas d'erreur, ne pas bloquer
        }
    }
}
?>