//! Shared validation utilities.
//!
//! This module provides common validation functions and pre-compiled regex patterns
//! for validating user input consistently across the application.

use std::sync::LazyLock;

use chattyness_error::AppError;
use regex::Regex;

// =============================================================================
// Pre-compiled Regex Patterns
// =============================================================================

/// Slug pattern: 1-50 characters, lowercase alphanumeric and hyphens.
/// Must start and end with alphanumeric (unless 1-2 chars).
static SLUG_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"^[a-z0-9][a-z0-9-]{1,48}[a-z0-9]$|^[a-z0-9]{1,2}$").expect("Invalid slug regex")
});

/// Hex color pattern: #RRGGBB or #RRGGBBAA format.
static HEX_COLOR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
    Regex::new(r"^#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$").expect("Invalid hex color regex")
});

/// Username pattern: 3-30 characters, starts with lowercase letter,
/// contains only lowercase letters, numbers, and underscores.
static USERNAME_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^[a-z][a-z0-9_]{2,29}$").expect("Invalid username regex"));

/// Email pattern: basic check for @ symbol with characters before and after.
static EMAIL_REGEX: LazyLock<Regex> =
    LazyLock::new(|| Regex::new(r"^[^\s@]+@[^\s@]+\.[^\s@]+$").expect("Invalid email regex"));

// =============================================================================
// Validation Functions
// =============================================================================

/// Validate a slug (URL-friendly identifier).
///
/// # Rules
/// - 1-50 characters
/// - Lowercase alphanumeric and hyphens only
/// - Must start and end with alphanumeric (except for 1-2 character slugs)
///
/// # Returns
/// - `Ok(())` if valid
/// - `Err(AppError::Validation)` if invalid
pub fn validate_slug(slug: &str) -> Result<(), AppError> {
    if !SLUG_REGEX.is_match(slug) {
        return Err(AppError::Validation(
            "Slug must be 1-50 characters, lowercase alphanumeric and hyphens only".to_string(),
        ));
    }
    Ok(())
}

/// Check if a slug is valid without returning an error.
pub fn is_valid_slug(slug: &str) -> bool {
    SLUG_REGEX.is_match(slug)
}

/// Validate a hex color string.
///
/// # Rules
/// - Must match #RRGGBB or #RRGGBBAA format
/// - Case insensitive for hex digits
///
/// # Returns
/// - `Ok(())` if valid
/// - `Err(AppError::Validation)` if invalid
pub fn validate_hex_color(color: &str) -> Result<(), AppError> {
    if !HEX_COLOR_REGEX.is_match(color) {
        return Err(AppError::Validation(
            "Color must be a valid hex color (#RRGGBB or #RRGGBBAA)".to_string(),
        ));
    }
    Ok(())
}

/// Check if a hex color is valid without returning an error.
pub fn is_valid_hex_color(color: &str) -> bool {
    HEX_COLOR_REGEX.is_match(color)
}

/// Validate an optional hex color string.
///
/// Returns `Ok(())` if the color is `None` or a valid hex color.
pub fn validate_optional_hex_color(color: Option<&str>) -> Result<(), AppError> {
    if let Some(c) = color {
        validate_hex_color(c)?;
    }
    Ok(())
}

/// Validate a username.
///
/// # Rules
/// - 3-30 characters
/// - Must start with a lowercase letter
/// - Can contain lowercase letters, numbers, and underscores
///
/// # Returns
/// - `Ok(())` if valid
/// - `Err(AppError::Validation)` if invalid
pub fn validate_username(username: &str) -> Result<(), AppError> {
    if !USERNAME_REGEX.is_match(username) {
        return Err(AppError::Validation(
            "Username must be 3-30 characters, start with a letter, and contain only lowercase letters, numbers, and underscores".to_string(),
        ));
    }
    Ok(())
}

/// Check if a username is valid without returning an error.
pub fn is_valid_username(username: &str) -> bool {
    USERNAME_REGEX.is_match(username)
}

/// Validate an email address.
///
/// # Rules
/// - Must contain @ with characters before and after
/// - Must have a domain with at least one dot
///
/// # Returns
/// - `Ok(())` if valid
/// - `Err(AppError::Validation)` if invalid
pub fn validate_email(email: &str) -> Result<(), AppError> {
    let email = email.trim();
    if email.is_empty() || !EMAIL_REGEX.is_match(email) {
        return Err(AppError::Validation("Invalid email address".to_string()));
    }
    Ok(())
}

/// Check if an email is valid without returning an error.
pub fn is_valid_email(email: &str) -> bool {
    let email = email.trim();
    !email.is_empty() && EMAIL_REGEX.is_match(email)
}

/// Validate that a string is not empty after trimming.
///
/// # Returns
/// - `Ok(())` if the string is non-empty
/// - `Err(AppError::Validation)` with the provided field name if empty
pub fn validate_non_empty(value: &str, field_name: &str) -> Result<(), AppError> {
    if value.trim().is_empty() {
        return Err(AppError::Validation(format!(
            "{} cannot be empty",
            field_name
        )));
    }
    Ok(())
}

/// Validate that a string length is within bounds.
///
/// # Returns
/// - `Ok(())` if length is within bounds
/// - `Err(AppError::Validation)` if too short or too long
pub fn validate_length(value: &str, field_name: &str, min: usize, max: usize) -> Result<(), AppError> {
    let len = value.len();
    if len < min || len > max {
        return Err(AppError::Validation(format!(
            "{} must be {}-{} characters",
            field_name, min, max
        )));
    }
    Ok(())
}

/// Validate that a number is within a range.
///
/// # Returns
/// - `Ok(())` if the number is within the range
/// - `Err(AppError::Validation)` if out of range
pub fn validate_range<T: std::cmp::PartialOrd + std::fmt::Display>(
    value: T,
    field_name: &str,
    min: T,
    max: T,
) -> Result<(), AppError> {
    if value < min || value > max {
        return Err(AppError::Validation(format!(
            "{} must be between {} and {}",
            field_name, min, max
        )));
    }
    Ok(())
}

/// Validate a password meets minimum requirements.
///
/// # Rules
/// - At least 8 characters
///
/// # Returns
/// - `Ok(())` if valid
/// - `Err(AppError::Validation)` if too short
pub fn validate_password(password: &str) -> Result<(), AppError> {
    if password.len() < 8 {
        return Err(AppError::Validation(
            "Password must be at least 8 characters".to_string(),
        ));
    }
    Ok(())
}

/// Validate that two passwords match.
///
/// # Returns
/// - `Ok(())` if passwords match
/// - `Err(AppError::Validation)` if they don't match
pub fn validate_passwords_match(password: &str, confirm: &str) -> Result<(), AppError> {
    if password != confirm {
        return Err(AppError::Validation("Passwords do not match".to_string()));
    }
    Ok(())
}

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

    #[test]
    fn test_validate_slug() {
        // Valid slugs
        assert!(validate_slug("a").is_ok());
        assert!(validate_slug("ab").is_ok());
        assert!(validate_slug("abc").is_ok());
        assert!(validate_slug("my-realm").is_ok());
        assert!(validate_slug("realm123").is_ok());
        assert!(validate_slug("my-awesome-realm-2024").is_ok());

        // Invalid slugs
        assert!(validate_slug("").is_err());
        assert!(validate_slug("-starts-with-dash").is_err());
        assert!(validate_slug("ends-with-dash-").is_err());
        assert!(validate_slug("HAS-UPPERCASE").is_err());
        assert!(validate_slug("has spaces").is_err());
        assert!(validate_slug("has_underscore").is_err());
    }

    #[test]
    fn test_validate_hex_color() {
        // Valid colors
        assert!(validate_hex_color("#ffffff").is_ok());
        assert!(validate_hex_color("#FFFFFF").is_ok());
        assert!(validate_hex_color("#000000").is_ok());
        assert!(validate_hex_color("#ff5733").is_ok());
        assert!(validate_hex_color("#FF573380").is_ok()); // With alpha

        // Invalid colors
        assert!(validate_hex_color("").is_err());
        assert!(validate_hex_color("ffffff").is_err()); // Missing #
        assert!(validate_hex_color("#fff").is_err()); // Too short
        assert!(validate_hex_color("#fffffff").is_err()); // 7 chars
        assert!(validate_hex_color("#gggggg").is_err()); // Invalid hex
    }

    #[test]
    fn test_validate_username() {
        // Valid usernames
        assert!(validate_username("abc").is_ok());
        assert!(validate_username("user123").is_ok());
        assert!(validate_username("my_user_name").is_ok());

        // Invalid usernames
        assert!(validate_username("ab").is_err()); // Too short
        assert!(validate_username("123abc").is_err()); // Starts with number
        assert!(validate_username("_user").is_err()); // Starts with underscore
        assert!(validate_username("User").is_err()); // Uppercase
        assert!(validate_username("user-name").is_err()); // Has hyphen
    }

    #[test]
    fn test_validate_email() {
        // Valid emails
        assert!(validate_email("user@example.com").is_ok());
        assert!(validate_email("test.user@domain.org").is_ok());

        // Invalid emails
        assert!(validate_email("").is_err());
        assert!(validate_email("noatsign").is_err());
        assert!(validate_email("@nodomain").is_err());
        assert!(validate_email("no@dotcom").is_err());
    }

    #[test]
    fn test_validate_password() {
        assert!(validate_password("12345678").is_ok());
        assert!(validate_password("longpassword123!").is_ok());
        assert!(validate_password("1234567").is_err()); // Too short
        assert!(validate_password("").is_err());
    }
}
