//! Module de validation des signatures pour le streaming
//!
//! Ce module implémente la validation HMAC-SHA256 des URLs de streaming
//! pour sécuriser l'accès aux fichiers audio.

use crate::error::{AppError, Result};
use hmac::{Hmac, Mac};
use serde::{Deserialize, Serialize};
use sha2::Sha256;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use tokio::sync::RwLock;
use uuid::Uuid;

type HmacSha256 = Hmac<Sha256>;

/// Configuration de sécurité pour les signatures
#[derive(Debug, Clone)]
pub struct SignatureConfig {
    pub secret_key: String,
    pub default_ttl: Duration,
    pub max_ttl: Duration,
}

/// Gestionnaire de validation des signatures
pub struct TokenValidator {
    config: SignatureConfig,
    active_tokens: Arc<RwLock<HashMap<String, TokenInfo>>>,
}

/// Informations sur un token actif
#[derive(Debug, Clone)]
pub struct TokenInfo {
    pub track_id: String,
    pub user_id: Option<Uuid>,
    pub created_at: SystemTime,
    pub expires_at: SystemTime,
    pub access_count: u32,
}

/// Paramètres de requête de streaming
#[derive(Debug, Deserialize)]
pub struct StreamRequest {
    pub track_id: String,
    pub expires: u64,
    pub sig: String,
    pub user_id: Option<Uuid>,
}

impl TokenValidator {
    /// Crée un nouveau validateur de tokens
    pub fn new(config: SignatureConfig) -> Self {
        Self {
            config,
            active_tokens: Arc::new(RwLock::new(HashMap::new())),
        }
    }

    /// Génère une signature HMAC-SHA256 pour une URL de streaming
    pub fn generate_signature(
        &self,
        track_id: &str,
        expires: u64,
        user_id: Option<Uuid>,
    ) -> Result<String> {
        let mut mac =
            HmacSha256::new_from_slice(self.config.secret_key.as_bytes()).map_err(|e| {
                AppError::SignatureError {
                    message: format!("HMAC creation failed: {}", e),
                }
            })?;

        // Construire le message à signer
        let message = format!(
            "{}:{}:{}",
            track_id,
            expires,
            user_id.map(|u| u.to_string()).unwrap_or_default()
        );

        mac.update(message.as_bytes());
        let result = mac.finalize();

        Ok(hex::encode(result.into_bytes()))
    }

    /// Valide une signature de streaming
    pub fn validate_signature(
        &self,
        track_id: &str,
        expires: u64,
        signature: &str,
        user_id: Option<Uuid>,
    ) -> Result<bool> {
        // Vérifier l'expiration
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|e| AppError::SignatureError {
                message: format!("Time error: {}", e),
            })?
            .as_secs();

        if expires < now {
            return Err(AppError::SignatureError {
                message: "Token expired".to_string(),
            });
        }

        // Générer la signature attendue
        let expected_signature = self.generate_signature(track_id, expires, user_id)?;

        // Comparaison constante du temps pour éviter les attaques par timing
        use subtle::ConstantTimeEq;
        let signature_bytes = match hex::decode(signature) {
            Ok(bytes) => bytes,
            Err(_) => return Ok(false),
        };
        
        let expected_bytes =
            hex::decode(&expected_signature).map_err(|e| AppError::SignatureError {
                message: format!("Invalid hex expected: {}", e),
            })?;

        Ok(signature_bytes.ct_eq(&expected_bytes).into())
    }

    /// Valide et enregistre un token de streaming
    pub async fn validate_and_register_token(&self, request: &StreamRequest) -> Result<TokenInfo> {
        // Valider la signature
        if !self.validate_signature(
            &request.track_id,
            request.expires,
            &request.sig,
            request.user_id,
        )? {
            return Err(AppError::SignatureError {
                message: "Invalid signature".to_string(),
            });
        }

        // Créer les informations du token
        let token_info = TokenInfo {
            track_id: request.track_id.clone(),
            user_id: request.user_id,
            created_at: SystemTime::now(),
            expires_at: SystemTime::UNIX_EPOCH + Duration::from_secs(request.expires),
            access_count: 0,
        };

        // Enregistrer le token actif
        let token_key = format!("{}:{}", request.track_id, request.expires);
        let mut active_tokens = self.active_tokens.write().await;
        active_tokens.insert(token_key, token_info.clone());

        Ok(token_info)
    }

    /// Enregistre l'accès à un token
    pub async fn record_token_access(&self, track_id: &str, expires: u64) -> Result<()> {
        let token_key = format!("{}:{}", track_id, expires);
        let mut active_tokens = self.active_tokens.write().await;

        if let Some(token_info) = active_tokens.get_mut(&token_key) {
            token_info.access_count += 1;
        }

        Ok(())
    }

    /// Nettoie les tokens expirés
    pub async fn cleanup_expired_tokens(&self) -> Result<()> {
        let now = SystemTime::now();
        let mut active_tokens = self.active_tokens.write().await;

        active_tokens.retain(|_, token_info| token_info.expires_at > now);

        Ok(())
    }

    /// Génère une URL de streaming sécurisée
    pub fn generate_secure_url(
        &self,
        track_id: &str,
        ttl: Option<Duration>,
        user_id: Option<Uuid>,
    ) -> Result<SecureStreamUrl> {
        let ttl = ttl.unwrap_or(self.config.default_ttl);

        // Limiter la TTL maximale
        let ttl = if ttl > self.config.max_ttl {
            self.config.max_ttl
        } else {
            ttl
        };

        let expires = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .map_err(|e| AppError::SignatureError {
                message: format!("Time error: {}", e),
            })?
            .as_secs()
            + ttl.as_secs();

        let signature = self.generate_signature(track_id, expires, user_id)?;

        Ok(SecureStreamUrl {
            track_id: track_id.to_string(),
            expires,
            signature,
            user_id,
        })
    }

    /// Obtient les statistiques des tokens
    pub async fn get_token_stats(&self) -> Result<TokenStats> {
        let active_tokens = self.active_tokens.read().await;

        let total_tokens = active_tokens.len();
        let now = SystemTime::now();

        let active_last_hour = active_tokens
            .values()
            .filter(|token| {
                now.duration_since(token.created_at)
                    .map(|d| d.as_secs() < 3600)
                    .unwrap_or(false)
            })
            .count();

        let total_accesses: u32 = active_tokens.values().map(|token| token.access_count).sum();

        Ok(TokenStats {
            total_tokens,
            active_last_hour,
            total_accesses,
        })
    }

    /// Valide les permissions d'accès à un track
    pub async fn validate_track_access(
        &self,
        track_id: &str,
        user_id: Option<Uuid>,
    ) -> Result<bool> {
        // Pour l'instant, on autorise tous les accès authentifiés
        // Dans une implémentation complète, on vérifierait les permissions spécifiques
        // en interrogeant la base de données ou un service d'autorisation

        // Vérifier que le track existe (simulation)
        if track_id.is_empty() {
            return Ok(false);
        }

        // Vérifier les permissions utilisateur si fourni
        if let Some(user_id) = user_id {
            // Ici on pourrait vérifier si l'utilisateur a le droit d'accéder à ce track
            // Par exemple : est-ce que l'utilisateur a acheté ce track ?
            // Ou est-ce que le track est public ?
            Ok(true) // Simplifié pour l'exemple
        } else {
            // Accès anonyme - vérifier si le track est public
            Ok(true) // Simplifié pour l'exemple
        }
    }
}

/// URL de streaming sécurisée
#[derive(Debug, Serialize)]
pub struct SecureStreamUrl {
    pub track_id: String,
    pub expires: u64,
    pub signature: String,
    pub user_id: Option<Uuid>,
}

impl SecureStreamUrl {
    /// Construit l'URL complète de streaming
    pub fn build_url(&self, base_url: &str) -> String {
        let mut url = format!("{}/stream/{}", base_url, self.track_id);
        url.push_str(&format!("?expires={}&sig={}", self.expires, self.signature));

        if let Some(user_id) = self.user_id {
            url.push_str(&format!("&user_id={}", user_id));
        }

        url
    }
}

/// Statistiques des tokens
#[derive(Debug, Serialize)]
pub struct TokenStats {
    pub total_tokens: usize,
    pub active_last_hour: usize,
    pub total_accesses: u32,
}

impl Default for TokenValidator {
    fn default() -> Self {
        // SECURITY: Default impl ne doit être utilisé QUE pour les tests
        // En production, utilisez TokenValidator::new() avec require_env_min_length("SECRET_KEY", 32)
        #[cfg(not(any(test, debug_assertions)))]
        {
            panic!("Default TokenValidator should not be used in production");
        }
        #[cfg(any(test, debug_assertions))]
        {
            Self::new(SignatureConfig {
                secret_key: "test_secret_key_minimum_32_characters_long".to_string(),
                default_ttl: Duration::from_secs(3600), // 1 heure
                max_ttl: Duration::from_secs(86400),    // 24 heures
            })
        }
    }
}

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

    #[test]
    fn test_signature_generation_and_validation() {
        let validator = TokenValidator::new(SignatureConfig {
            secret_key: "test_secret".to_string(),
            default_ttl: Duration::from_secs(3600),
            max_ttl: Duration::from_secs(86400),
        });

        let track_id = "test_track_123";
        let expires = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
            + 3600;
        let user_id = Some(Uuid::new_v4());

        // Générer la signature
        let signature = validator
            .generate_signature(track_id, expires, user_id)
            .unwrap();

        // Valider la signature
        let is_valid = validator
            .validate_signature(track_id, expires, &signature, user_id)
            .unwrap();
        assert!(is_valid);

        // Test avec une signature invalide
        let invalid_signature = "invalid_signature";
        let is_invalid = validator
            .validate_signature(track_id, expires, invalid_signature, user_id)
            .unwrap();
        assert!(!is_invalid);
    }

    #[test]
    fn test_expired_token() {
        let validator = TokenValidator::new(SignatureConfig {
            secret_key: "test_secret".to_string(),
            default_ttl: Duration::from_secs(3600),
            max_ttl: Duration::from_secs(86400),
        });

        let track_id = "test_track_123";
        let expired_time = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
            - 3600; // Expiré il y a 1 heure

        let signature = validator
            .generate_signature(track_id, expired_time, None)
            .unwrap();

        let result = validator.validate_signature(track_id, expired_time, &signature, None);
        assert!(result.is_err());
    }

    #[tokio::test]
    async fn test_token_registration() {
        let validator = TokenValidator::new(SignatureConfig {
            secret_key: "test_secret".to_string(),
            default_ttl: Duration::from_secs(3600),
            max_ttl: Duration::from_secs(86400),
        });

        let expires = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs()
            + 3600;

        let request = StreamRequest {
            track_id: "test_track_123".to_string(),
            expires,
            sig: validator
                .generate_signature("test_track_123", expires, None)
                .unwrap(),
            user_id: None,
        };

        let token_info = validator
            .validate_and_register_token(&request)
            .await
            .unwrap();
        assert_eq!(token_info.track_id, "test_track_123");
        assert_eq!(token_info.access_count, 0);
    }
}
