package dev.lions.unionflow.client.security;

import dev.lions.unionflow.server.api.dto.auth.response.LoginResponse;
import dev.lions.unionflow.client.service.AuthenticationService;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

/**
 * Service de gestion et rafraîchissement des tokens JWT
 *
 * SÉCURITÉ:
 * - Limite de 10000 tokens actifs pour éviter les fuites mémoire
 * - Nettoyage automatique des tokens expirés
 * - Logging minimal pour éviter l'exposition de données sensibles
 *
 * @author UnionFlow Team
 * @version 2.0
 */
@ApplicationScoped
public class TokenRefreshService {

    private static final Logger LOGGER = Logger.getLogger(TokenRefreshService.class.getName());

    // SÉCURITÉ: Limite maximale de tokens stockés pour éviter les fuites mémoire
    private static final int MAX_TOKENS = 10_000;

    @Inject
    private AuthenticationService authService;

    // Stockage des tokens au niveau application avec limite de taille
    private final Map<String, TokenInfo> activeTokens = new ConcurrentHashMap<>();
    
    private static class TokenInfo {
        String accessToken;
        String refreshToken;
        long expirationTime;
        String sessionId;
        
        TokenInfo(String accessToken, String refreshToken, long expirationTime, String sessionId) {
            this.accessToken = accessToken;
            this.refreshToken = refreshToken;
            this.expirationTime = expirationTime;
            this.sessionId = sessionId;
        }
        
        boolean needsRefresh() {
            // Rafraîchir 5 minutes avant l'expiration
            long timeUntilExpiration = expirationTime - System.currentTimeMillis();
            return timeUntilExpiration < 300000; // 5 minutes en millisecondes
        }
        
        boolean isExpired() {
            return System.currentTimeMillis() > expirationTime;
        }
    }
    
    public void registerToken(String sessionId, String accessToken, String refreshToken, long expiresIn) {
        if (sessionId != null && accessToken != null) {
            // SÉCURITÉ: Vérifier la limite de taille du cache
            if (activeTokens.size() >= MAX_TOKENS) {
                // Nettoyer les tokens expirés avant d'en ajouter un nouveau
                cleanupExpiredTokens();

                // Si toujours trop de tokens, supprimer les plus anciens
                if (activeTokens.size() >= MAX_TOKENS) {
                    LOGGER.warning("Cache de tokens plein (" + MAX_TOKENS + " entrées). Nettoyage forcé.");
                    removeOldestTokens(MAX_TOKENS / 10); // Supprimer 10% des tokens
                }
            }

            long expirationTime = System.currentTimeMillis() + (expiresIn * 1000);
            activeTokens.put(sessionId, new TokenInfo(accessToken, refreshToken, expirationTime, sessionId));
            LOGGER.fine("Token enregistré avec succès"); // SÉCURITÉ: Ne pas logger le sessionId
        }
    }

    public void removeToken(String sessionId) {
        if (sessionId != null) {
            activeTokens.remove(sessionId);
            LOGGER.fine("Token supprimé"); // SÉCURITÉ: Ne pas logger le sessionId
        }
    }

    /**
     * Supprime les N tokens les plus anciens du cache
     * SÉCURITÉ: Prévention des fuites mémoire
     */
    private void removeOldestTokens(int count) {
        activeTokens.entrySet().stream()
            .sorted((e1, e2) -> Long.compare(e1.getValue().expirationTime, e2.getValue().expirationTime))
            .limit(count)
            .forEach(entry -> activeTokens.remove(entry.getKey()));

        LOGGER.info("Suppression de " + count + " tokens les plus anciens du cache");
    }
    
    // Cette méthode n'est plus appelée par le scheduler pour éviter les problèmes de contexte
    // Elle peut être appelée manuellement depuis un contexte avec session active
    public void checkAndRefreshTokens(String sessionId) {
        try {
            TokenInfo tokenInfo = activeTokens.get(sessionId);

            if (tokenInfo != null && tokenInfo.needsRefresh() && tokenInfo.refreshToken != null) {
                LOGGER.fine("Rafraîchissement du token JWT nécessaire"); // SÉCURITÉ: Ne pas logger le sessionId
                
                LoginResponse refreshedResponse = authService.refreshToken(tokenInfo.refreshToken);
                
                if (refreshedResponse != null) {
                    // Mettre à jour les tokens stockés
                    registerToken(sessionId, 
                                refreshedResponse.getAccessToken(),
                                refreshedResponse.getRefreshToken(),
                                refreshedResponse.getExpiresIn());
                    
                    LOGGER.fine("Token JWT rafraîchi avec succès"); // SÉCURITÉ
                } else {
                    LOGGER.warning("Échec du rafraîchissement du token JWT"); // SÉCURITÉ
                    handleTokenRefreshFailure(sessionId);
                }
            }
        } catch (Exception e) {
            LOGGER.severe("Erreur lors du rafraîchissement du token: " + e.getMessage());
            handleTokenRefreshFailure(sessionId);
        }
    }
    
    public boolean tryRefreshTokenNow(String sessionId) {
        try {
            TokenInfo tokenInfo = activeTokens.get(sessionId);
            
            if (tokenInfo != null && tokenInfo.refreshToken != null) {
                LoginResponse refreshedResponse = authService.refreshToken(tokenInfo.refreshToken);
                
                if (refreshedResponse != null) {
                    registerToken(sessionId,
                                refreshedResponse.getAccessToken(),
                                refreshedResponse.getRefreshToken(),
                                refreshedResponse.getExpiresIn());
                    
                    LOGGER.fine("Token rafraîchi manuellement avec succès"); // SÉCURITÉ
                    return true;
                }
            }
        } catch (Exception e) {
            LOGGER.warning("Échec du rafraîchissement manuel du token: " + e.getMessage());
        }
        
        return false;
    }
    
    private void handleTokenRefreshFailure(String sessionId) {
        // En cas d'échec du rafraîchissement, supprimer le token
        removeToken(sessionId);
        LOGGER.info("Session expirée - token supprimé"); // SÉCURITÉ
    }
    
    public boolean isTokenExpired(String sessionId) {
        TokenInfo tokenInfo = activeTokens.get(sessionId);
        return tokenInfo == null || tokenInfo.isExpired();
    }
    
    public long getTimeUntilExpiration(String sessionId) {
        TokenInfo tokenInfo = activeTokens.get(sessionId);
        if (tokenInfo != null) {
            long timeLeft = tokenInfo.expirationTime - System.currentTimeMillis();
            return Math.max(0, timeLeft / 1000); // Retourner en secondes
        }
        return 0;
    }
    
    public String getAccessToken(String sessionId) {
        TokenInfo tokenInfo = activeTokens.get(sessionId);
        return tokenInfo != null ? tokenInfo.accessToken : null;
    }
    
    /**
     * Méthode pour nettoyer les tokens expirés périodiquement
     * SÉCURITÉ: Libération de la mémoire et protection contre les fuites
     */
    public void cleanupExpiredTokens() {
        int initialSize = activeTokens.size();
        activeTokens.entrySet().removeIf(entry -> entry.getValue().isExpired());
        int removedCount = initialSize - activeTokens.size();

        if (removedCount > 0) {
            LOGGER.info("Nettoyage automatique: " + removedCount + " token(s) expiré(s) supprimé(s)");
        }
    }

    /**
     * Retourne le nombre de tokens actifs dans le cache
     * Utile pour le monitoring et les métriques
     */
    public int getActiveTokenCount() {
        return activeTokens.size();
    }
}
