//! Password hashing and verification using Argon2id
//! 
//! Provides secure password hashing with memory-hard Argon2id algorithm,
//! resistant to GPU and ASIC attacks.

use crate::error::{AuthError, Result};
use argon2::{
    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
    Argon2, Params,
};
use rand::rngs::OsRng;

/// Hash a password using Argon2id
/// 
/// Uses memory-hard Argon2id with secure parameters:
/// - Memory cost: 64 MiB (65536 KiB)
/// - Time cost: 3 iterations
/// - Parallelism: 4 lanes
/// 
/// # Arguments
/// 
/// * `password` - The plaintext password to hash
/// 
/// # Returns
/// 
/// A PHC string formatted hash that includes the algorithm, parameters, salt, and hash
/// 
/// # Security
/// 
/// - Uses cryptographically secure random salt
/// - Memory-hard algorithm resistant to GPU attacks
/// - Follows OWASP recommendations
/// 
/// # Example
/// 
/// ```rust
/// use irene_auth::hash_password;
/// 
/// let hash = hash_password("my_secure_password").unwrap();
/// // Returns: "$argon2id$v=19$m=65536,t=3,p=4$..."
/// ```
pub fn hash_password(password: &str) -> Result<String> {
    // Argon2id parameters (balanced security vs performance)
    let params = Params::new(
        65536, // 64 MiB memory
        3,     // 3 iterations
        4,     // 4 parallel lanes
        None,  // Default output length
    )
    .map_err(|e| AuthError::PasswordHashError(e.to_string()))?;

    let argon2 = Argon2::new(
        argon2::Algorithm::Argon2id,
        argon2::Version::V0x13,
        params,
    );

    // Generate cryptographically secure random salt
    let salt = SaltString::generate(&mut OsRng);

    // Hash password
    let password_hash = argon2
        .hash_password(password.as_bytes(), &salt)
        .map_err(|e| AuthError::PasswordHashError(e.to_string()))?;

    Ok(password_hash.to_string())
}

/// Verify a password against a hash using constant-time comparison
/// 
/// # Arguments
/// 
/// * `password` - The plaintext password to verify
/// * `password_hash` - The PHC formatted hash string to verify against
/// 
/// # Returns
/// 
/// `Ok(true)` if password matches, `Err(AuthError)` if not
/// 
/// # Security
/// 
/// - Uses constant-time comparison to prevent timing attacks
/// - Automatically extracts and uses the same parameters from the hash
/// 
/// # Example
/// 
/// ```rust
/// use irene_auth::{hash_password, verify_password};
/// 
/// let hash = hash_password("correct_password").unwrap();
/// 
/// // Correct password
/// assert!(verify_password("correct_password", &hash).is_ok());
/// 
/// // Wrong password
/// assert!(verify_password("wrong_password", &hash).is_err());
/// ```
pub fn verify_password(password: &str, password_hash: &str) -> Result<bool> {
    // Parse the stored hash
    let parsed_hash = PasswordHash::new(password_hash)
        .map_err(|_| AuthError::InvalidPasswordHashFormat)?;

    // Create Argon2 instance with default parameters
    let argon2 = Argon2::default();

    // Verify password (uses constant-time comparison)
    argon2
        .verify_password(password.as_bytes(), &parsed_hash)
        .map_err(|_| AuthError::PasswordVerificationFailed)?;

    Ok(true)
}

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

    #[test]
    fn test_hash_password() {
        let hash = hash_password("test_password").unwrap();
        
        // Should start with Argon2id identifier
        assert!(hash.starts_with("$argon2id$"));
        
        // Should be reasonably long
        assert!(hash.len() > 50);
    }

    #[test]
    fn test_verify_password_correct() {
        let password = "my_secure_password";
        let hash = hash_password(password).unwrap();
        
        assert!(verify_password(password, &hash).is_ok());
    }

    #[test]
    fn test_verify_password_incorrect() {
        let hash = hash_password("correct_password").unwrap();
        
        let result = verify_password("wrong_password", &hash);
        assert!(result.is_err());
    }

    #[test]
    fn test_hash_uniqueness() {
        let password = "same_password";
        let hash1 = hash_password(password).unwrap();
        let hash2 = hash_password(password).unwrap();
        
        // Should produce different hashes due to random salt
        assert_ne!(hash1, hash2);
        
        // But both should verify correctly
        assert!(verify_password(password, &hash1).is_ok());
        assert!(verify_password(password, &hash2).is_ok());
    }

    #[test]
    fn test_invalid_hash_format() {
        let result = verify_password("password", "invalid_hash");
        assert!(matches!(result, Err(AuthError::InvalidPasswordHashFormat)));
    }
}
