use argon2::{
    Argon2,
    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString, rand_core::OsRng},
};
use rand::Rng;

#[derive(Debug, Clone)]
pub struct PasswordService {
    argon2: Argon2<'static>,
}

impl PasswordService {
    pub fn new() -> Self {
        // Use Argon2id variant (recommended)
        let argon2 = Argon2::default();
        Self { argon2 }
    }

    /// Create a PasswordService with faster parameters for testing
    #[cfg(test)]
    pub fn new_for_testing() -> Self {
        use argon2::Params;
        // Use minimal but valid parameters for faster testing
        let params = Params::new(1024, 1, 1, None).unwrap(); // 1KB memory, 1 iteration, 1 thread
        let argon2 = Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, params);
        Self { argon2 }
    }

    /// Hash a password using Argon2
    pub fn hash_password(&self, password: &str) -> Result<String, argon2::password_hash::Error> {
        let salt = SaltString::generate(&mut OsRng);
        let password_hash = self.argon2.hash_password(password.as_bytes(), &salt)?;
        Ok(password_hash.to_string())
    }

    /// Verify a password against a hash
    pub fn verify_password(
        &self,
        password: &str,
        hash: &str,
    ) -> Result<bool, argon2::password_hash::Error> {
        let parsed_hash = PasswordHash::new(hash)?;
        self.argon2
            .verify_password(password.as_bytes(), &parsed_hash)
            .map(|_| true)
            .or_else(|err| match err {
                argon2::password_hash::Error::Password => Ok(false),
                _ => Err(err),
            })
    }

    /// Generate a random password
    #[allow(dead_code)]
    pub fn generate_random_password(&self, length: usize) -> String {
        const CHARSET: &[u8] =
            b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*";
        let mut rng = rand::rng();

        (0..length)
            .map(|_| {
                let idx = rng.random_range(0..CHARSET.len());
                CHARSET[idx] as char
            })
            .collect()
    }

    /// Generate a secure random token (for password reset, email verification, etc.)
    pub fn generate_secure_token(&self) -> String {
        use rand::RngCore;
        let mut rng = rand::rng();
        let mut bytes = [0u8; 32];
        rng.fill_bytes(&mut bytes);
        hex::encode(bytes)
    }

    /// Validate password strength
    pub fn validate_password_strength(&self, password: &str) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();

        if password.len() < 8 {
            errors.push("Password must be at least 8 characters long".to_string());
        }

        if password.len() > 128 {
            errors.push("Password must be no more than 128 characters long".to_string());
        }

        if !password.chars().any(|c| c.is_ascii_uppercase()) {
            errors.push("Password must contain at least one uppercase letter".to_string());
        }

        if !password.chars().any(|c| c.is_ascii_lowercase()) {
            errors.push("Password must contain at least one lowercase letter".to_string());
        }

        if !password.chars().any(|c| c.is_ascii_digit()) {
            errors.push("Password must contain at least one number".to_string());
        }

        if !password.chars().any(|c| !c.is_alphanumeric()) {
            errors.push("Password must contain at least one special character".to_string());
        }

        // Check for common passwords
        if self.is_common_password(password) {
            errors.push("Password is too common. Please choose a more unique password".to_string());
        }

        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }

    /// Check if password is in common passwords list
    fn is_common_password(&self, password: &str) -> bool {
        // List of common passwords to avoid
        const COMMON_PASSWORDS: &[&str] = &[
            "password",
            "123456",
            "password123",
            "admin",
            "qwerty",
            "letmein",
            "welcome",
            "monkey",
            "1234567890",
            "abc123",
            "111111",
            "123123",
            "password1",
            "1234",
            "12345",
            "dragon",
            "master",
            "hello",
            "login",
            "welcome123",
            "admin123",
            "root",
            "pass",
            "test",
            "guest",
            "123456789",
            "qwerty123",
            "password12",
            "letmein123",
        ];

        let lower_password = password.to_lowercase();
        COMMON_PASSWORDS.contains(&lower_password.as_str())
    }

    /// Check if password has been pwned (in a real implementation, this would check against Have I Been Pwned API)
    #[allow(dead_code)]
    pub async fn check_password_pwned(
        &self,
        _password: &str,
    ) -> Result<bool, Box<dyn std::error::Error>> {
        // In a real implementation, you would:
        // 1. Hash the password with SHA-1
        // 2. Take the first 5 characters of the hash
        // 3. Query the Have I Been Pwned API with those 5 characters
        // 4. Check if the remaining hash appears in the response

        // For now, we'll just return false
        Ok(false)
    }

    /// Generate a password reset token with expiration
    pub fn generate_password_reset_token(&self) -> (String, chrono::DateTime<chrono::Utc>) {
        let token = self.generate_secure_token();
        let expires_at = chrono::Utc::now() + chrono::Duration::hours(24); // Token expires in 24 hours
        (token, expires_at)
    }

    /// Generate an email verification token
    #[allow(dead_code)]
    pub fn generate_email_verification_token(&self) -> (String, chrono::DateTime<chrono::Utc>) {
        let token = self.generate_secure_token();
        let expires_at = chrono::Utc::now() + chrono::Duration::hours(48); // Token expires in 48 hours
        (token, expires_at)
    }

    /// Validate token format
    #[allow(dead_code)]
    pub fn validate_token_format(&self, token: &str) -> bool {
        // Check if token is a valid hex string of expected length
        token.len() == 64 && token.chars().all(|c| c.is_ascii_hexdigit())
    }
}

impl Default for PasswordService {
    fn default() -> Self {
        Self::new()
    }
}

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

    #[test]
    fn test_password_hashing() {
        let service = PasswordService::new_for_testing();
        let password = "test_password_123!";

        let hash = service
            .hash_password(password)
            .expect("Failed to hash password");
        assert!(
            service
                .verify_password(password, &hash)
                .expect("Failed to verify correct password")
        );
        assert!(
            !service
                .verify_password("wrong_password", &hash)
                .expect("Failed to verify incorrect password")
        );
    }

    #[test]
    fn test_password_strength_validation() {
        let service = PasswordService::new();

        // Valid password
        assert!(service.validate_password_strength("StrongPass123!").is_ok());

        // Too short
        assert!(service.validate_password_strength("Short1!").is_err());

        // No uppercase
        assert!(service.validate_password_strength("weakpass123!").is_err());

        // No lowercase
        assert!(service.validate_password_strength("WEAKPASS123!").is_err());

        // No numbers
        assert!(service.validate_password_strength("WeakPass!").is_err());

        // No special characters
        assert!(service.validate_password_strength("WeakPass123").is_err());

        // Common password
        assert!(service.validate_password_strength("password123").is_err());
    }

    #[test]
    fn test_random_password_generation() {
        let service = PasswordService::new();

        let password1 = service.generate_random_password(12);
        let password2 = service.generate_random_password(12);

        assert_eq!(password1.len(), 12);
        assert_eq!(password2.len(), 12);
        assert_ne!(password1, password2); // Should be different
    }

    #[test]
    fn test_secure_token_generation() {
        let service = PasswordService::new();

        let token1 = service.generate_secure_token();
        let token2 = service.generate_secure_token();

        assert_eq!(token1.len(), 64); // 32 bytes = 64 hex characters
        assert_eq!(token2.len(), 64);
        assert_ne!(token1, token2); // Should be different
        assert!(service.validate_token_format(&token1));
        assert!(service.validate_token_format(&token2));
    }

    #[test]
    fn test_token_validation() {
        let service = PasswordService::new();

        let valid_token = service.generate_secure_token();
        assert!(service.validate_token_format(&valid_token));

        // Invalid tokens
        assert!(!service.validate_token_format("too_short"));
        assert!(!service.validate_token_format(&"z".repeat(64))); // Non-hex characters
        assert!(!service.validate_token_format(&"a".repeat(63))); // Wrong length
    }

    #[test]
    fn test_common_password_detection() {
        let service = PasswordService::new();

        assert!(service.is_common_password("password"));
        assert!(service.is_common_password("123456"));
        assert!(service.is_common_password("PASSWORD")); // Case insensitive
        assert!(!service.is_common_password("UniquePassword123!"));
    }
}
