//! Authentication utilities for Veza Rust services
//! 
//! This module provides authentication-related utilities and helpers.

use uuid::Uuid;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::{VezaError, VezaResult};

/// JWT claims structure
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct JwtClaims {
    pub sub: Uuid,        // Subject (user ID)
    pub iss: String,      // Issuer
    pub aud: String,      // Audience
    pub exp: u64,         // Expiration time
    pub iat: u64,         // Issued at
    pub nbf: Option<u64>, // Not before
    pub jti: Option<String>, // JWT ID
    pub username: String,
    pub email: String,
    pub roles: Vec<String>,
}

impl JwtClaims {
    /// Create new JWT claims
    pub fn new(
        user_id: Uuid,
        username: String,
        email: String,
        roles: Vec<String>,
        issuer: String,
        audience: String,
        ttl_seconds: u64,
    ) -> Self {
        let now = Utc::now().timestamp() as u64;
        
        Self {
            sub: user_id,
            iss: issuer,
            aud: audience,
            exp: now + ttl_seconds,
            iat: now,
            nbf: Some(now),
            jti: Some(uuid::Uuid::new_v4().to_string()),
            username,
            email,
            roles,
        }
    }
    
    /// Check if claims are expired
    pub fn is_expired(&self) -> bool {
        let now = Utc::now().timestamp() as u64;
        self.exp < now
    }
    
    /// Check if claims are valid (not before)
    pub fn is_valid(&self) -> bool {
        let now = Utc::now().timestamp() as u64;
        if let Some(nbf) = self.nbf {
            now >= nbf
        } else {
            true
        }
    }
    
    /// Check if user has role
    pub fn has_role(&self, role: &str) -> bool {
        self.roles.contains(&role.to_string())
    }
    
    /// Check if user has any of the roles
    pub fn has_any_role(&self, roles: &[&str]) -> bool {
        roles.iter().any(|role| self.has_role(role))
    }
}

/// Authentication result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthResult {
    pub access_token: String,
    pub refresh_token: String,
    pub token_type: String,
    pub expires_in: u64,
    pub user_id: Uuid,
    pub username: String,
    pub email: String,
    pub roles: Vec<String>,
}

/// Password validation result
#[derive(Debug, Clone)]
pub struct PasswordValidation {
    pub is_valid: bool,
    pub errors: Vec<String>,
    pub strength_score: u8, // 0-100
}

impl PasswordValidation {
    /// Create new password validation
    pub fn new(password: &str) -> Self {
        let mut errors = Vec::new();
        let mut strength_score = 0u8;
        
        // Length check
        if password.len() >= 8 {
            strength_score += 20;
        } else {
            errors.push("Password must be at least 8 characters long".to_string());
        }
        
        if password.len() >= 12 {
            strength_score += 10;
        }
        
        // Character variety checks
        if password.chars().any(|c| c.is_uppercase()) {
            strength_score += 20;
        } else {
            errors.push("Password must contain at least one uppercase letter".to_string());
        }
        
        if password.chars().any(|c| c.is_lowercase()) {
            strength_score += 20;
        } else {
            errors.push("Password must contain at least one lowercase letter".to_string());
        }
        
        if password.chars().any(|c| c.is_numeric()) {
            strength_score += 20;
        } else {
            errors.push("Password must contain at least one digit".to_string());
        }
        
        if password.chars().any(|c| c.is_ascii_punctuation()) {
            strength_score += 20;
        } else {
            errors.push("Password must contain at least one special character".to_string());
        }
        
        // Additional strength checks
        if password.len() >= 16 {
            strength_score += 10;
        }
        
        if password.chars().filter(|c| c.is_ascii_punctuation()).count() >= 2 {
            strength_score += 10;
        }
        
        let is_valid = errors.is_empty();
        
        Self {
            is_valid,
            errors,
            strength_score,
        }
    }
}

/// Session information
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInfo {
    pub session_id: Uuid,
    pub user_id: Uuid,
    pub ip_address: Option<String>,
    pub user_agent: Option<String>,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub last_activity: DateTime<Utc>,
    pub is_active: bool,
}

impl SessionInfo {
    /// Check if session is expired
    pub fn is_expired(&self) -> bool {
        Utc::now() > self.expires_at
    }
    
    /// Check if session is valid
    pub fn is_valid(&self) -> bool {
        self.is_active && !self.is_expired()
    }
    
    /// Update last activity
    pub fn update_activity(&mut self) {
        self.last_activity = Utc::now();
    }
}

/// Permission check result
#[derive(Debug, Clone)]
pub struct PermissionCheck {
    pub allowed: bool,
    pub reason: Option<String>,
}

impl PermissionCheck {
    /// Create allowed permission check
    pub fn allowed() -> Self {
        Self {
            allowed: true,
            reason: None,
        }
    }
    
    /// Create denied permission check
    pub fn denied(reason: String) -> Self {
        Self {
            allowed: false,
            reason: Some(reason),
        }
    }
}

/// Role-based access control
pub struct RBAC {
    roles: std::collections::HashMap<String, Vec<String>>,
}

impl RBAC {
    /// Create new RBAC instance
    pub fn new() -> Self {
        Self {
            roles: std::collections::HashMap::new(),
        }
    }
    
    /// Add role with permissions
    pub fn add_role(&mut self, role: &str, permissions: Vec<String>) {
        self.roles.insert(role.to_string(), permissions);
    }
    
    /// Check if role has permission
    pub fn has_permission(&self, role: &str, permission: &str) -> bool {
        self.roles
            .get(role)
            .map(|permissions| permissions.contains(&permission.to_string()))
            .unwrap_or(false)
    }
    
    /// Check if user has permission (check all roles)
    pub fn user_has_permission(&self, user_roles: &[String], permission: &str) -> bool {
        user_roles.iter().any(|role| self.has_permission(role, permission))
    }
    
    /// Get all permissions for a role
    pub fn get_role_permissions(&self, role: &str) -> Vec<String> {
        self.roles
            .get(role)
            .cloned()
            .unwrap_or_default()
    }
}

impl Default for RBAC {
    fn default() -> Self {
        let mut rbac = Self::new();
        
        // Add default roles
        rbac.add_role("admin", vec![
            "user:read".to_string(),
            "user:write".to_string(),
            "user:delete".to_string(),
            "conversation:read".to_string(),
            "conversation:write".to_string(),
            "conversation:delete".to_string(),
            "track:read".to_string(),
            "track:write".to_string(),
            "track:delete".to_string(),
            "system:admin".to_string(),
        ]);
        
        rbac.add_role("user", vec![
            "user:read".to_string(),
            "conversation:read".to_string(),
            "conversation:write".to_string(),
            "track:read".to_string(),
            "track:write".to_string(),
        ]);
        
        rbac.add_role("guest", vec![
            "track:read".to_string(),
        ]);
        
        rbac
    }
}

/// Generate secure random password
pub fn generate_secure_password(length: usize) -> VezaResult<String> {
    use rand::Rng;
    
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
    let mut rng = rand::thread_rng();
    
    let password: String = (0..length)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    
    Ok(password)
}

/// Generate recovery code
pub fn generate_recovery_code() -> VezaResult<String> {
    use rand::Rng;
    
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    let mut rng = rand::thread_rng();
    
    let code: String = (0..8)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    
    Ok(code)
}

/// Generate TOTP secret
pub fn generate_totp_secret() -> VezaResult<String> {
    use rand::Rng;
    
    const CHARSET: &[u8] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let mut rng = rand::thread_rng();
    
    let secret: String = (0..32)
        .map(|_| {
            let idx = rng.gen_range(0..CHARSET.len());
            CHARSET[idx] as char
        })
        .collect();
    
    Ok(secret)
}

/// Validate TOTP code
pub fn validate_totp_code(secret: &str, code: &str, _window: i64) -> VezaResult<bool> {
    use totp_rs::{TOTP, Algorithm, Secret};
    
    // totp-rs 5.7 API: TOTP::new takes 7 arguments: algorithm, digits, skew, step, secret, issuer, account_name
    // Use Secret::Encoded to handle base32 string directly
    let secret_obj = Secret::Encoded(secret.to_string());
    
    let totp = TOTP::new(
        Algorithm::SHA1,
        6,
        1,
        30,
        secret_obj.to_bytes()
            .map_err(|e| VezaError::Auth(format!("Invalid TOTP secret: {}", e)))?,
        Some("Veza".to_string()),  // issuer
        "user".to_string(),         // account_name (generic, can be customized)
    ).map_err(|e| VezaError::Auth(format!("Invalid TOTP secret: {}", e)))?;
    
    let is_valid = totp.check_current(code)
        .map_err(|e| VezaError::Auth(format!("TOTP validation error: {}", e)))?;
    
    Ok(is_valid)
}

/// Generate QR code data for TOTP setup
pub fn generate_totp_qr_data(secret: &str, username: &str, issuer: &str) -> String {
    format!(
        "otpauth://totp/{}:{}?secret={}&issuer={}",
        issuer, username, secret, issuer
    )
}
