//! This module provides the `PasswordConfig` struct and related methods for
//! generating secure and customizable passwords. It's designed to be a
//! flexible library that can be used in various applications, from simple
//! command line tools to web services.

use serde::{Serialize, Deserialize};
use rand::{seq::SliceRandom, prelude::IndexedRandom};
use crate::errors::{PasswordError};

/// A configuration struct for generating passwords.
///
/// This struct allows you to customize the length and character sets of
/// the generated password.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PasswordConfig {
  /// The desired length of the password.
  pub length: usize,
  /// Whether to include uppercase letters (A-Z).
  pub include_uppercase: bool,
  /// Whether to include lowercase letters (a-z).
  pub include_lowercase: bool,
  /// Whether to include numbers (0-9).
  pub include_numbers: bool,
  /// Whether to include symbols.
  pub include_symbols: bool,
  /// Whether to exclude ambiguous characters (e.g., O, 0, l, 1).
  pub exclude_ambiguous: bool,
  /// Whether to exclude similar characters (e.g., i, l, 1).
  pub exclude_similar: bool,
  /// Whether to require at least one character from each included set.
  pub require_each_type: bool
}

impl Default for PasswordConfig {
  /// Returns a default password configuration.

  /// The default configuration generates a 16 character password with
  /// uppercase, lowercase, numbers, and symbols included.
  fn default() -> Self {
    Self {
      length: 16,
      include_uppercase: true,
      include_lowercase: true,
      include_numbers: true,
      include_symbols: true,
      exclude_ambiguous: false,
      exclude_similar: false,
      require_each_type: false
    }
  }
}

impl PasswordConfig {
  /// Creates a new `PasswordConfig` with a specified length and default settings.
  pub fn new(length: usize) -> Self {
    Self {
      length,
      ..Default::default()
    }
  }

  /// Sets the password length.
  pub fn with_length(mut self, length: usize) -> Self {
    self.length = length;
    self
  }

  /// Sets whether to include uppercase letters.
  pub fn with_uppercase(mut self, include: bool) -> Self {
    self.include_uppercase = include;
    self
  }

  /// Sets whether to include lowercase letters.
  pub fn with_lowercase(mut self, include: bool) -> Self {
    self.include_lowercase = include;
    self
  }

  /// Sets whether to include numbers.
  pub fn with_numbers(mut self, include: bool) -> Self {
    self.include_numbers = include;
    self
  }

  /// Sets whether to include symbols.
  pub fn with_symbols(mut self, include: bool) -> Self {
    self.include_symbols = include;
    self
  }

  /// Sets whether to exclude ambiguous characters (O, 0, l, 1, etc.).
  pub fn with_exclude_ambiguous(mut self, exclude: bool) -> Self {
    self.exclude_ambiguous = exclude;
    self
  }

  /// Sets whether to exclude characters that look similar (i, l, 1, etc.).
  pub fn with_exclude_similar(mut self, exclude: bool) -> Self {
    self.exclude_similar = exclude;
    self
  }

  /// Sets whether to require at least one character from each enabled type.
  pub fn with_require_each_type(mut self, require: bool) -> Self {
    self.require_each_type = require;
    self
  }

  /// Validates the password configuration.
  ///
  /// This method checks for common configuration errors before password
  /// generation begins. It returns a `PasswordError` if the configuration
  /// is invalid.
  pub fn validate(&self) -> Result<(), PasswordError> {
    if self.length == 0 {
      return Err(PasswordError::LengthTooShort)
    }

    if self.length > 128 {
      return Err(PasswordError::LengthTooLong)
    }

    if self.require_each_type && (!self.include_uppercase || !self.include_lowercase || !self.include_numbers || !self.include_symbols) {
      return Err(PasswordError::InvalidConfig("You've enabled `require_each_type`, but have also excluded one or more character sets.".to_string()));
    }

    if !self.include_uppercase && !self.include_lowercase && !self.include_numbers && !self.include_symbols {
      return Err(PasswordError::InvalidConfig("At least one character type must be included.".to_string()));
    }

    Ok(())
  }

  /// Generates a password based on the current configuration.
  ///
  /// This method first validates the configuration and then generates a
  /// cryptographically secure password using the specified character sets.
  pub fn generate(&self) -> Result<String, PasswordError> {
    self.validate()?;

    let mut password: Vec<char> = Vec::new();
    let mut rng = rand::rng();

    let require_all = self.require_each_type;
    let include_uppercase = self.include_uppercase || require_all;
    let include_lowercase = self.include_lowercase || require_all;
    let include_numbers = self.include_numbers || require_all;
    let include_symbols = self.include_symbols || require_all;

    let uppercase_chars: Vec<char> = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".chars().collect();
    let lowercase_chars: Vec<char> = "abcdefghijklmnopqrstuvwxyz".chars().collect();
    let number_chars: Vec<char> = "0123456789".chars().collect();
    let symbol_chars: Vec<char> = "!@#$%^&*()_+-=[]{}|;':\",.<>/?`~".chars().collect();
 
    if include_uppercase {
      password.push(*uppercase_chars.choose(&mut rng).unwrap());
    }
    if include_lowercase {
      password.push(*lowercase_chars.choose(&mut rng).unwrap());
    }
    if include_numbers {
      password.push(*number_chars.choose(&mut rng).unwrap());
    }
    if include_symbols {
      password.push(*symbol_chars.choose(&mut rng).unwrap());
    }

    let mut full_char_set = Vec::new();
    if include_uppercase { full_char_set.extend_from_slice(&uppercase_chars); }
    if include_lowercase { full_char_set.extend_from_slice(&lowercase_chars); }
    if include_numbers { full_char_set.extend_from_slice(&number_chars); }
    if include_symbols { full_char_set.extend_from_slice(&symbol_chars); }

    let remaining_length = self.length.saturating_sub(password.len());
    for _ in 0..remaining_length {
      let random_char = *full_char_set.choose(&mut rng).unwrap();
        password.push(random_char);
    }

    password.shuffle(&mut rng);

    Ok(password.into_iter().collect())
  }
}

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

  fn has_uppercase(password: &str) -> bool {
    password.chars().any(|c| c.is_ascii_uppercase())
  }

  fn has_lowercase(password: &str) -> bool {
    password.chars().any(|c| c.is_ascii_lowercase())
  }

  fn has_number(password: &str) -> bool {
    password.chars().any(|c| c.is_ascii_digit())
  }

  fn has_symbol(password: &str) -> bool {
    password.chars().any(|c| "!@#$%^&*()_+-=[]{}|;':\",.<>/?`~".contains(c))
  }

  #[test]
  fn validate_default_config() {
    let config = PasswordConfig::default();
    let password = config.generate().unwrap();

    assert_eq!(password.len(), config.length);

    assert!(has_uppercase(&password));
    assert!(has_lowercase(&password));
    assert!(has_number(&password));
    assert!(has_symbol(&password));
  }

  #[test]
  fn validate_password_length_too_short() {
    let mut config = PasswordConfig::default();
    config.length = 0;

    let validation = config.validate();

    assert!(validation.is_err());
    assert_eq!(validation.unwrap_err(), PasswordError::LengthTooShort);
  }

  #[test]
  fn validate_only_uppercase_and_numbers() {
    let config = PasswordConfig::default()
      .with_uppercase(true)
      .with_lowercase(false)
      .with_numbers(true)
      .with_symbols(false);

    let password = config.generate().unwrap();

    assert!(has_uppercase(&password));
    assert!(!has_lowercase(&password));
    assert!(has_number(&password));
    assert!(!has_symbol(&password));
  }

  #[test]
  fn validate_only_uppercase() {
    let config = PasswordConfig::default()
      .with_uppercase(true)
      .with_lowercase(false)
      .with_numbers(false)
      .with_symbols(false);
      
    let password = config.generate().unwrap();
    assert!(has_uppercase(&password));
    assert!(!has_lowercase(&password));
    assert!(!has_number(&password));
    assert!(!has_symbol(&password));
  }

  #[test]
  fn generate_with_only_numbers_and_symbols() {
    let config = PasswordConfig::default()
      .with_uppercase(false)
      .with_lowercase(false)
      .with_numbers(true)
      .with_symbols(true);

    let password = config.generate().unwrap();
    assert!(!has_uppercase(&password));
    assert!(!has_lowercase(&password));
    assert!(has_number(&password));
    assert!(has_symbol(&password));
  }
}