//! Protection CSRF (Cross-Site Request Forgery)
//!
//! Ce module implémente la protection CSRF avec:
//! - Génération de tokens CSRF sécurisés
//! - Validation des tokens CSRF
//! - Rotation automatique des tokens
//! - Protection contre les attaques CSRF

use crate::error::{ChatError, Result};
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use uuid::Uuid;

/// Claims pour les tokens CSRF
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CsrfTokenClaims {
    /// ID unique du token
    pub jti: String,
    /// Type de token
    pub token_type: String,
    /// Audience
    pub aud: String,
    /// Issuer
    pub iss: String,
    /// Sujet (session_id)
    pub sub: String,
    /// Expiration
    pub exp: usize,
    /// Émis à
    pub iat: usize,
    /// Nonce pour la protection contre les attaques de rejeu
    pub nonce: String,
}

/// Gestionnaire CSRF
pub struct CsrfManager {
    /// Clé de signature
    encoding_key: EncodingKey,
    /// Clé de vérification
    decoding_key: DecodingKey,
    /// Configuration de validation
    validation: Validation,
    /// Tokens CSRF actifs (jti -> session_id)
    active_tokens: Arc<RwLock<HashMap<String, String>>>,
    /// Configuration
    secret: String,
    /// Durée de vie des tokens CSRF (en secondes)
    token_lifetime: i64,
}

impl CsrfManager {
    /// Crée un nouveau gestionnaire CSRF
    pub fn new(secret: String, token_lifetime: i64) -> Result<Self> {
        let algorithm = Algorithm::HS256;
        let encoding_key = EncodingKey::from_secret(secret.as_bytes());
        let decoding_key = DecodingKey::from_secret(secret.as_bytes());

        let mut validation = Validation::new(algorithm);
        validation.set_audience(&["veza-csrf"]);
        validation.set_issuer(&["veza-platform"]);
        validation.set_required_spec_claims(&["exp", "iat", "sub", "aud", "iss", "jti", "nonce"]);

        Ok(Self {
            encoding_key,
            decoding_key,
            validation,
            active_tokens: Arc::new(RwLock::new(HashMap::new())),
            secret,
            token_lifetime,
        })
    }

    /// Génère un nouveau token CSRF
    pub async fn generate_token(&self, session_id: &str) -> Result<String> {
        let now = Utc::now();
        let exp = now + Duration::seconds(self.token_lifetime);
        let jti = Uuid::new_v4().to_string();
        let nonce = Uuid::new_v4().to_string();

        let claims = CsrfTokenClaims {
            jti: jti.clone(),
            token_type: "csrf".to_string(),
            aud: "veza-csrf".to_string(),
            iss: "veza-platform".to_string(),
            sub: session_id.to_string(),
            exp: exp.timestamp() as usize,
            iat: now.timestamp() as usize,
            nonce,
        };

        let algorithm = Algorithm::HS256;
        let header = Header::new(algorithm);
        let token = encode(&header, &claims, &self.encoding_key)
            .map_err(|e| ChatError::internal_error(format!("Erreur génération token CSRF: {e}")))?;

        // Enregistrer le token comme actif
        {
            let mut active_tokens = self.active_tokens.write().await;
            active_tokens.insert(jti.clone(), session_id.to_string());
        }

        tracing::debug!(
            session_id = %session_id,
            jti = %jti,
            exp = %exp.timestamp(),
            "🔐 Token CSRF généré"
        );

        Ok(token)
    }

    /// Valide un token CSRF
    pub async fn validate_token(&self, token: &str, session_id: &str) -> Result<()> {
        // Décoder le token
        let token_data = decode::<CsrfTokenClaims>(token, &self.decoding_key, &self.validation)
            .map_err(|e| {
                tracing::warn!(error = %e, "❌ Token CSRF invalide");
                ChatError::unauthorized("Token CSRF invalide")
            })?;

        let claims = &token_data.claims;

        // Vérifier que le token est actif
        {
            let active_tokens = self.active_tokens.read().await;
            if !active_tokens.contains_key(&claims.jti) {
                tracing::warn!(jti = %claims.jti, "❌ Token CSRF révoqué");
                return Err(ChatError::unauthorized("Token CSRF révoqué"));
            }
        }

        // Vérifier que le token correspond à la session
        if claims.sub != session_id {
            tracing::warn!(
                token_session = %claims.sub,
                expected_session = %session_id,
                "❌ Token CSRF ne correspond pas à la session"
            );
            return Err(ChatError::unauthorized(
                "Token CSRF ne correspond pas à la session",
            ));
        }

        // Vérifier l'expiration
        let now = Utc::now().timestamp() as usize;
        if claims.exp < now {
            tracing::warn!(exp = %claims.exp, now = %now, "❌ Token CSRF expiré");
            return Err(ChatError::unauthorized("Token CSRF expiré"));
        }

        tracing::debug!(
            session_id = %session_id,
            jti = %claims.jti,
            "✅ Token CSRF validé"
        );

        Ok(())
    }

    /// Révoque un token CSRF
    pub async fn revoke_token(&self, token: &str) -> Result<()> {
        let token_data = decode::<CsrfTokenClaims>(token, &self.decoding_key, &self.validation)
            .map_err(|e| {
                tracing::warn!(error = %e, "❌ Token CSRF invalide lors de la révocation");
                ChatError::unauthorized("Token CSRF invalide")
            })?;

        let jti = &token_data.claims.jti;
        {
            let mut active_tokens = self.active_tokens.write().await;
            active_tokens.remove(jti);
        }

        tracing::debug!(jti = %jti, "🔐 Token CSRF révoqué");

        Ok(())
    }

    /// Révoque tous les tokens d'une session
    pub async fn revoke_session_tokens(&self, session_id: &str) -> Result<()> {
        let mut active_tokens = self.active_tokens.write().await;
        active_tokens.retain(|_, session| session != session_id);

        tracing::debug!(session_id = %session_id, "🔐 Tous les tokens CSRF de la session révoqués");

        Ok(())
    }

    /// Nettoie les tokens expirés
    pub async fn cleanup_expired_tokens(&self) -> Result<()> {
        let _now = Utc::now().timestamp() as usize;
        let mut active_tokens = self.active_tokens.write().await;
        let initial_count = active_tokens.len();

        // Note: Dans une implémentation réelle, on devrait vérifier l'expiration
        // des tokens stockés. Ici, on se contente de nettoyer périodiquement.
        active_tokens.retain(|_, _| true); // Placeholder pour la logique de nettoyage

        let final_count = active_tokens.len();
        let cleaned = initial_count - final_count;

        if cleaned > 0 {
            tracing::debug!(cleaned = %cleaned, "🧹 Tokens CSRF expirés nettoyés");
        }

        Ok(())
    }

    /// Obtient le nombre de tokens actifs
    pub async fn active_token_count(&self) -> usize {
        let active_tokens = self.active_tokens.read().await;
        active_tokens.len()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[tokio::test]
    async fn test_csrf_token_generation_and_validation() {
        let manager = CsrfManager::new("test-secret".to_string(), 3600).unwrap();
        let session_id = "test-session-123";

        // Générer un token
        let token = manager.generate_token(session_id).await.unwrap();
        assert!(!token.is_empty());

        // Valider le token
        let result = manager.validate_token(&token, session_id).await;
        assert!(result.is_ok());

        // Valider avec un mauvais session_id
        let result = manager.validate_token(&token, "wrong-session").await;
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_csrf_token_revocation() {
        let manager = CsrfManager::new("test-secret".to_string(), 3600).unwrap();
        let session_id = "test-session-123";

        // Générer un token
        let token = manager.generate_token(session_id).await.unwrap();

        // Valider le token
        let result = manager.validate_token(&token, session_id).await;
        assert!(result.is_ok());

        // Révoquer le token
        let result = manager.revoke_token(&token).await;
        assert!(result.is_ok());

        // Valider le token révoqué
        let result = manager.validate_token(&token, session_id).await;
        assert!(result.is_err());
    }
}
