//! Validation utilities for Veza services
//! 
//! This module provides validation functions for common data types
//! such as email addresses, usernames, passwords, URLs, etc.

use crate::error::{CommonError, CommonResult};
use lazy_static::lazy_static;
use regex::Regex;

lazy_static! {
    // Email validation regex - RFC 5322 compliant
    static ref EMAIL_REGEX: Regex = Regex::new(
        r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
    ).unwrap();
    
    // Username validation regex - 3-30 characters, alphanumeric and underscores
    static ref USERNAME_REGEX: Regex = Regex::new(
        r"^[a-zA-Z0-9_]{3,30}$"
    ).unwrap();
    
    // Password validation regex - at least 8 characters, alphanumeric and special chars
    // Note: We'll validate letter and number requirements separately
    static ref PASSWORD_REGEX: Regex = Regex::new(
        r"^[A-Za-z\d@$!%*#?&]{8,}$"
    ).unwrap();
    
    // URL validation regex
    static ref URL_REGEX: Regex = Regex::new(
        r"^https?://(?:[-\w.])+(?:[:\d]+)?(?:/(?:[\w/_.])*)?(?:\?(?:[\w&=%.])*)?(?:#(?:[\w.])*)?$"
    ).unwrap();
    
    // Phone number validation regex - international format (E.164)
    // Must be 10-15 digits, optionally starting with +
    static ref PHONE_REGEX: Regex = Regex::new(
        r"^\+?[1-9]\d{9,14}$"
    ).unwrap();
}

/// Validate an email address
/// 
/// Returns true if the email address is valid according to RFC 5322.
/// 
/// # Examples
/// ```
/// use veza_common::utils::validation::validate_email;
/// 
/// assert!(validate_email("test@example.com"));
/// assert!(validate_email("user.name@example.co.uk"));
/// assert!(!validate_email("invalid-email"));
/// ```
pub fn validate_email(email: &str) -> bool {
    if email.is_empty() || email.len() > 254 {
        return false;
    }
    
    // Check basic structure: must contain @ and .
    if !email.contains('@') || !email.contains('.') {
        return false;
    }
    
    // Split by @ and check parts
    let parts: Vec<&str> = email.split('@').collect();
    if parts.len() != 2 {
        return false;
    }
    
    let domain = parts[1];
    // Domain must contain at least one dot after @
    if !domain.contains('.') {
        return false;
    }
    
    EMAIL_REGEX.is_match(email)
}

/// Validate an email address and return a Result
/// 
/// Returns Ok(()) if the email is valid, or an error if invalid.
pub fn validate_email_result(email: &str) -> CommonResult<()> {
    if validate_email(email) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(format!("Invalid email address: {}", email)))
    }
}

/// Validate a username
/// 
/// Username must be:
/// - 3 to 30 characters long
/// - Contains only alphanumeric characters and underscores
/// 
/// # Examples
/// ```
/// use veza_common::utils::validation::validate_username;
/// 
/// assert!(validate_username("user123"));
/// assert!(validate_username("test_user"));
/// assert!(!validate_username("ab")); // Too short
/// assert!(!validate_username("user-name")); // Contains hyphen
/// ```
pub fn validate_username(username: &str) -> bool {
    USERNAME_REGEX.is_match(username)
}

/// Validate a username and return a Result
/// 
/// Returns Ok(()) if the username is valid, or an error if invalid.
pub fn validate_username_result(username: &str) -> CommonResult<()> {
    if validate_username(username) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(
            format!("Invalid username: must be 3-30 characters, alphanumeric and underscores only")
        ))
    }
}

/// Validate a password
/// 
/// Password must be:
/// - At least 8 characters long
/// - Contains at least one letter
/// - Contains at least one number
/// - May contain special characters: @$!%*#?&
/// 
/// # Examples
/// ```
/// use veza_common::utils::validation::validate_password;
/// 
/// assert!(validate_password("Password123"));
/// assert!(validate_password("MyP@ssw0rd"));
/// assert!(!validate_password("short")); // Too short
/// assert!(!validate_password("NoNumbers")); // No numbers
/// ```
pub fn validate_password(password: &str) -> bool {
    if password.is_empty() || password.len() < 8 {
        return false;
    }
    
    // Check format (alphanumeric and allowed special chars)
    if !PASSWORD_REGEX.is_match(password) {
        return false;
    }
    
    // Check for at least one letter
    let has_letter = password.chars().any(|c| c.is_alphabetic());
    if !has_letter {
        return false;
    }
    
    // Check for at least one number
    let has_number = password.chars().any(|c| c.is_ascii_digit());
    if !has_number {
        return false;
    }
    
    true
}

/// Validate a password and return a Result
/// 
/// Returns Ok(()) if the password is valid, or an error if invalid.
pub fn validate_password_result(password: &str) -> CommonResult<()> {
    if validate_password(password) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(
            format!("Invalid password: must be at least 8 characters with at least one letter and one number")
        ))
    }
}

/// Validate a URL
/// 
/// Validates HTTP/HTTPS URLs.
/// 
/// # Examples
/// ```
/// use veza_common::utils::validation::validate_url;
/// 
/// assert!(validate_url("https://example.com"));
/// assert!(validate_url("http://example.com/path?query=value"));
/// assert!(!validate_url("not-a-url"));
/// ```
pub fn validate_url(url: &str) -> bool {
    if url.is_empty() {
        return false;
    }
    URL_REGEX.is_match(url)
}

/// Validate a URL and return a Result
/// 
/// Returns Ok(()) if the URL is valid, or an error if invalid.
pub fn validate_url_result(url: &str) -> CommonResult<()> {
    if validate_url(url) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(format!("Invalid URL: {}", url)))
    }
}

/// Validate a phone number
/// 
/// Validates international phone number format (E.164).
/// 
/// # Examples
/// ```
/// use veza_common::utils::validation::validate_phone;
/// 
/// assert!(validate_phone("+1234567890"));
/// assert!(validate_phone("1234567890"));
/// assert!(!validate_phone("123")); // Too short
/// ```
pub fn validate_phone(phone: &str) -> bool {
    if phone.is_empty() {
        return false;
    }
    PHONE_REGEX.is_match(phone)
}

/// Validate a phone number and return a Result
/// 
/// Returns Ok(()) if the phone number is valid, or an error if invalid.
pub fn validate_phone_result(phone: &str) -> CommonResult<()> {
    if validate_phone(phone) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(format!("Invalid phone number: {}", phone)))
    }
}

/// Validate a string length
/// 
/// Returns true if the string length is within the specified range (inclusive).
pub fn validate_length(value: &str, min: usize, max: usize) -> bool {
    let len = value.len();
    len >= min && len <= max
}

/// Validate a string length and return a Result
pub fn validate_length_result(value: &str, min: usize, max: usize) -> CommonResult<()> {
    if validate_length(value, min, max) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(
            format!("String length must be between {} and {} characters", min, max)
        ))
    }
}

/// Validate that a string is not empty
pub fn validate_not_empty(value: &str) -> bool {
    !value.trim().is_empty()
}

/// Validate that a string is not empty and return a Result
pub fn validate_not_empty_result(value: &str) -> CommonResult<()> {
    if validate_not_empty(value) {
        Ok(())
    } else {
        Err(CommonError::ValidationError("Value cannot be empty".to_string()))
    }
}

/// Validate a numeric range
/// 
/// Returns true if the value is within the specified range (inclusive).
pub fn validate_range<T: PartialOrd>(value: &T, min: &T, max: &T) -> bool {
    value >= min && value <= max
}

/// Validate a numeric range and return a Result
pub fn validate_range_result<T: PartialOrd + std::fmt::Display + Clone>(value: T, min: T, max: T) -> CommonResult<()> {
    if validate_range(&value, &min, &max) {
        Ok(())
    } else {
        Err(CommonError::ValidationError(
            format!("Value must be between {} and {}", min, max)
        ))
    }
}

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

    #[test]
    fn test_validate_email() {
        // Valid emails
        assert!(validate_email("test@example.com"));
        assert!(validate_email("user.name@example.com"));
        assert!(validate_email("user+tag@example.co.uk"));
        assert!(validate_email("user_name@example.com"));
        assert!(validate_email("user123@example123.com"));
        assert!(validate_email("a@b.co"));
        
        // Invalid emails
        assert!(!validate_email(""));
        assert!(!validate_email("invalid-email"));
        assert!(!validate_email("@example.com"));
        assert!(!validate_email("test@"));
        assert!(!validate_email("test@.com"));
        assert!(!validate_email("test @example.com"));
        assert!(!validate_email("test@example"));
        assert!(!validate_email(&"a".repeat(255))); // Too long
    }

    #[test]
    fn test_validate_email_result() {
        assert!(validate_email_result("test@example.com").is_ok());
        assert!(validate_email_result("invalid-email").is_err());
    }

    #[test]
    fn test_validate_username() {
        // Valid usernames
        assert!(validate_username("user123"));
        assert!(validate_username("test_user"));
        assert!(validate_username("abc"));
        assert!(validate_username("User123"));
        assert!(validate_username("_user_"));
        assert!(validate_username(&"a".repeat(30))); // Max length
        
        // Invalid usernames
        assert!(!validate_username(""));
        assert!(!validate_username("ab")); // Too short
        assert!(!validate_username(&"a".repeat(31))); // Too long
        assert!(!validate_username("user-name")); // Contains hyphen
        assert!(!validate_username("user name")); // Contains space
        assert!(!validate_username("user.name")); // Contains dot
        assert!(!validate_username("user@name")); // Contains @
    }

    #[test]
    fn test_validate_username_result() {
        assert!(validate_username_result("user123").is_ok());
        assert!(validate_username_result("ab").is_err());
    }

    #[test]
    fn test_validate_password() {
        // Valid passwords
        assert!(validate_password("Password123"));
        assert!(validate_password("MyP@ssw0rd"));
        assert!(validate_password("test123456"));
        assert!(validate_password("ABCdef123"));
        assert!(validate_password("Pass@123"));
        
        // Invalid passwords
        assert!(!validate_password(""));
        assert!(!validate_password("short")); // Too short
        assert!(!validate_password("NoNumbers")); // No numbers
        assert!(!validate_password("12345678")); // No letters
        assert!(!validate_password("abcdefgh")); // No numbers
    }

    #[test]
    fn test_validate_password_result() {
        assert!(validate_password_result("Password123").is_ok());
        assert!(validate_password_result("short").is_err());
    }

    #[test]
    fn test_validate_url() {
        // Valid URLs
        assert!(validate_url("https://example.com"));
        assert!(validate_url("http://example.com"));
        assert!(validate_url("https://example.com/path"));
        assert!(validate_url("https://example.com/path?query=value"));
        assert!(validate_url("https://example.com/path?query=value#fragment"));
        assert!(validate_url("http://subdomain.example.com"));
        
        // Invalid URLs
        assert!(!validate_url(""));
        assert!(!validate_url("not-a-url"));
        assert!(!validate_url("example.com")); // Missing protocol
        assert!(!validate_url("ftp://example.com")); // Unsupported protocol
    }

    #[test]
    fn test_validate_url_result() {
        assert!(validate_url_result("https://example.com").is_ok());
        assert!(validate_url_result("not-a-url").is_err());
    }

    #[test]
    fn test_validate_phone() {
        // Valid phone numbers
        assert!(validate_phone("+1234567890"));
        assert!(validate_phone("1234567890"));
        assert!(validate_phone("+12345678901234"));
        
        // Invalid phone numbers
        assert!(!validate_phone(""));
        assert!(!validate_phone("123")); // Too short (less than 10 digits)
        assert!(!validate_phone("123456789")); // Too short (9 digits)
        assert!(!validate_phone("+0123456789")); // Starts with 0
        assert!(!validate_phone("123-456-7890")); // Contains hyphens
        assert!(!validate_phone("(123) 456-7890")); // Contains parentheses
    }

    #[test]
    fn test_validate_phone_result() {
        assert!(validate_phone_result("+1234567890").is_ok());
        assert!(validate_phone_result("123").is_err());
    }

    #[test]
    fn test_validate_length() {
        assert!(validate_length("abc", 3, 5));
        assert!(validate_length("abc", 3, 3));
        assert!(validate_length("abcde", 3, 5));
        assert!(!validate_length("ab", 3, 5)); // Too short
        assert!(!validate_length("abcdef", 3, 5)); // Too long
    }

    #[test]
    fn test_validate_length_result() {
        assert!(validate_length_result("abc", 3, 5).is_ok());
        assert!(validate_length_result("ab", 3, 5).is_err());
    }

    #[test]
    fn test_validate_not_empty() {
        assert!(validate_not_empty("test"));
        assert!(validate_not_empty(" test "));
        assert!(!validate_not_empty(""));
        assert!(!validate_not_empty("   "));
    }

    #[test]
    fn test_validate_not_empty_result() {
        assert!(validate_not_empty_result("test").is_ok());
        assert!(validate_not_empty_result("").is_err());
    }

    #[test]
    fn test_validate_range() {
        assert!(validate_range(&5, &1, &10));
        assert!(validate_range(&1, &1, &10));
        assert!(validate_range(&10, &1, &10));
        assert!(!validate_range(&0, &1, &10)); // Too low
        assert!(!validate_range(&11, &1, &10)); // Too high
    }

    #[test]
    fn test_validate_range_result() {
        assert!(validate_range_result(5, 1, 10).is_ok());
        assert!(validate_range_result(0, 1, 10).is_err());
    }
}

