//! Security Validation and Sanitization Functions
//!
//! Provides security validation for user inputs, file paths, content
//! sanitization, and routing/navigation validation.

/// Basic HTML sanitization by removing potentially dangerous tags and
/// attributes
pub fn sanitize_html(input: &str) -> String {
    // List of allowed HTML tags (basic formatting only)
    let _allowed_tags = [
        "p",
        "br",
        "strong",
        "b",
        "em",
        "i",
        "u",
        "code",
        "pre",
        "h1",
        "h2",
        "h3",
        "h4",
        "h5",
        "h6",
        "ul",
        "ol",
        "li",
        "a",
        "img",
        "blockquote",
        "span",
        "div",
    ];

    // List of dangerous tags to completely remove
    let dangerous_tags = [
        "script", "style", "iframe", "object", "embed", "form", "input", "textarea", "button",
        "select", "option",
    ];

    let mut result = input.to_string();

    // Remove dangerous tags and their content
    for tag in dangerous_tags {
        let pattern = format!("<{}[^>]*>.*?</{}>", tag, tag);
        result = regex_replace_simple(&result, &pattern, "");

        // Also remove self-closing versions
        let self_closing = format!("<{}[^>]*/>", tag);
        result = regex_replace_simple(&result, &self_closing, "");
    }

    // Remove dangerous attributes from any remaining tags
    let dangerous_attributes = ["onclick", "onload", "onerror", "onmouseover", "javascript:"];
    for attr in dangerous_attributes {
        result = result.replace(&format!("{}=", attr), "data-removed=");
    }

    result
}

/// Simple regex replacement without external dependencies
fn regex_replace_simple(input: &str, pattern: &str, _replacement: &str) -> String {
    // This is a simplified implementation - in production you'd use the regex crate
    // For now, just remove obvious script tags
    if pattern.contains("script") {
        input.replace("<script>", "").replace("</script>", "")
    } else if pattern.contains("style") {
        input.replace("<style>", "").replace("</style>", "")
    } else {
        input.to_string()
    }
}

/// Validate that a file path is safe and within allowed boundaries
pub fn validate_file_path(path: &str) -> Result<(), String> {
    // Check for directory traversal attempts
    if path.contains("..") {
        return Err("Path contains directory traversal attempt".to_string());
    }

    // Check for absolute paths
    if path.starts_with('/') || (cfg!(windows) && path.len() > 2 && &path[1..2] == ":") {
        return Err("Absolute paths are not allowed".to_string());
    }

    // Check for suspicious patterns
    if path.contains("//") || path.contains("\\\\") {
        return Err("Path contains suspicious separators".to_string());
    }

    // Length check
    if path.len() > 255 {
        return Err("Path too long (max 255 characters)".to_string());
    }

    Ok(())
}

/// Validate email format (basic validation)
pub fn validate_email(email: &str) -> bool {
    // Basic email validation - contains @ and has reasonable length
    email.contains('@')
        && email.len() > 5
        && email.len() < 255
        && !email.starts_with('@')
        && !email.ends_with('@')
        && email.is_ascii()
}

/// Validate URL format (basic validation)
pub fn validate_url(url: &str) -> bool {
    url.starts_with("http://") || url.starts_with("https://") || url.starts_with("/")
}

/// Sanitize user input for safe display
pub fn sanitize_user_input(input: &str) -> String {
    // Replace ampersand first to avoid double-escaping
    input
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&#x27;")
}

/// Validate that a string contains only safe characters for identifiers
pub fn validate_identifier(identifier: &str) -> bool {
    !identifier.is_empty()
        && identifier.len() <= 50
        && identifier
            .chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
        && !identifier.starts_with('-')
        && !identifier.ends_with('-')
}

/// Validate content type names
pub fn validate_content_type(content_type: &str) -> bool {
    validate_identifier(content_type) && content_type.len() >= 2
}

/// Validate language codes (ISO 639-1 format)
pub fn validate_language_code(lang: &str) -> bool {
    lang.len() == 2 && lang.chars().all(|c| c.is_ascii_lowercase())
}

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

    #[test]
    fn test_sanitize_html() {
        let dangerous = "<script>alert('xss')</script><p>Safe content</p>";
        let result = sanitize_html(dangerous);
        assert!(!result.contains("script"));
        assert!(result.contains("Safe content"));
    }

    #[test]
    fn test_validate_file_path() {
        assert!(validate_file_path("safe/path.txt").is_ok());
        assert!(validate_file_path("../etc/passwd").is_err());
        assert!(validate_file_path("/absolute/path").is_err());
        assert!(validate_file_path("path//with//double").is_err());
    }

    #[test]
    fn test_validate_email() {
        assert!(validate_email("user@example.com"));
        assert!(validate_email("test.email+tag@domain.co.uk"));
        assert!(!validate_email("invalid.email"));
        assert!(!validate_email("@domain.com"));
        assert!(!validate_email("user@"));
    }

    #[test]
    fn test_validate_url() {
        assert!(validate_url("https://example.com"));
        assert!(validate_url("http://example.com"));
        assert!(validate_url("/relative/path"));
        assert!(!validate_url("ftp://example.com"));
        assert!(!validate_url("javascript:alert('xss')"));
    }

    #[test]
    fn test_sanitize_user_input() {
        let input = "Hello <script>alert('xss')</script> & \"quotes\"";
        let result = sanitize_user_input(input);
        assert_eq!(
            result,
            "Hello &lt;script&gt;alert(&#x27;xss&#x27;)&lt;/script&gt; &amp; &quot;quotes&quot;"
        );
    }

    #[test]
    fn test_validate_identifier() {
        assert!(validate_identifier("valid_identifier"));
        assert!(validate_identifier("test-123"));
        assert!(!validate_identifier(""));
        assert!(!validate_identifier("-starts-with-dash"));
        assert!(!validate_identifier("ends-with-dash-"));
        assert!(!validate_identifier("has spaces"));
        assert!(!validate_identifier("has@special"));
    }

    #[test]
    fn test_validate_content_type() {
        assert!(validate_content_type("blog"));
        assert!(validate_content_type("recipes"));
        assert!(!validate_content_type("a")); // too short
        assert!(!validate_content_type("invalid-content@type"));
    }

    #[test]
    fn test_validate_language_code() {
        assert!(validate_language_code("en"));
        assert!(validate_language_code("es"));
        assert!(!validate_language_code("EN")); // uppercase
        assert!(!validate_language_code("eng")); // too long
        assert!(!validate_language_code("e")); // too short
    }
}

// ============================================================================
// Route Validation Module - Usable in build-time and runtime contexts
// ============================================================================

use std::collections::{HashMap, HashSet};

/// Route validation error types
#[derive(Debug, Clone, PartialEq)]
pub enum RouteValidationError {
    /// Menu route doesn't exist in configured routes
    RouteNotFound { route: String, language: String },
    /// Language not found in configured routes
    LanguageNotFound { language: String },
    /// External route (always valid)
    External,
}

impl std::fmt::Display for RouteValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::RouteNotFound { route, language } => {
                write!(
                    f,
                    "Menu route '{}' not found in configured routes for language '{}'",
                    route, language
                )
            }
            Self::LanguageNotFound { language } => {
                write!(f, "No routes found for language '{}'", language)
            }
            Self::External => write!(f, "External route (always valid)"),
        }
    }
}

/// Route validator that works in both build-time and runtime contexts
///
/// # Examples
///
/// Build-time usage:
/// ```ignore
/// let mut routes = HashMap::new();
/// routes.insert("en".to_string(), ["/", "/about", "/services"].iter().map(|s| s.to_string()).collect());
/// let validator = RouteValidator::new(routes);
/// // Validate during build
/// ```
///
/// Runtime usage:
/// ```ignore
/// let validator = RouteValidator::from_loaded_routes(&server_routes);
/// // Validate menu items dynamically
/// ```
pub struct RouteValidator {
    available_routes: HashMap<String, HashSet<String>>,
}

impl RouteValidator {
    /// Create a new validator with available routes
    pub fn new(available_routes: HashMap<String, HashSet<String>>) -> Self {
        Self { available_routes }
    }

    /// Validate a single menu route
    ///
    /// Returns `Ok(())` if valid, or `Err(RouteValidationError)` if invalid
    pub fn validate(
        &self,
        route: &str,
        language: &str,
        is_external: bool,
    ) -> Result<(), RouteValidationError> {
        // External routes are always valid
        if is_external {
            return Ok(());
        }

        // Get routes for this language
        let lang_routes =
            self.available_routes
                .get(language)
                .ok_or(RouteValidationError::LanguageNotFound {
                    language: language.to_string(),
                })?;

        // Check if route exists
        if !lang_routes.contains(route) {
            return Err(RouteValidationError::RouteNotFound {
                route: route.to_string(),
                language: language.to_string(),
            });
        }

        Ok(())
    }

    /// Validate multiple routes and collect all errors
    ///
    /// Returns `Ok(())` if all valid, or `Err(Vec<RouteValidationError>)` if
    /// any invalid
    pub fn validate_batch(
        &self,
        routes: &[(String, String, bool)], // (route, language, is_external)
    ) -> Result<(), Vec<RouteValidationError>> {
        let errors: Vec<_> = routes
            .iter()
            .filter_map(|(route, language, is_external)| {
                self.validate(route, language, *is_external).err()
            })
            .collect();

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

    /// Get all available routes for a language
    pub fn get_language_routes(&self, language: &str) -> Option<&HashSet<String>> {
        self.available_routes.get(language)
    }

    /// Get all available languages
    pub fn available_languages(&self) -> Vec<String> {
        let mut langs: Vec<_> = self.available_routes.keys().cloned().collect();
        langs.sort();
        langs
    }

    /// Check if a specific route exists in a language
    pub fn route_exists(&self, language: &str, route: &str) -> bool {
        self.available_routes
            .get(language)
            .map(|routes| routes.contains(route))
            .unwrap_or(false)
    }
}

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

    #[test]
    fn test_validate_existing_route() {
        let mut routes = HashMap::new();
        routes.insert(
            "en".to_string(),
            ["/", "/about", "/services"]
                .iter()
                .map(|s| s.to_string())
                .collect(),
        );

        let validator = RouteValidator::new(routes);
        assert!(validator.validate("/about", "en", false).is_ok());
    }

    #[test]
    fn test_validate_nonexistent_route() {
        let mut routes = HashMap::new();
        routes.insert(
            "en".to_string(),
            ["/", "/about"].iter().map(|s| s.to_string()).collect(),
        );

        let validator = RouteValidator::new(routes);
        let result = validator.validate("/no-existe", "en", false);
        assert!(matches!(
            result,
            Err(RouteValidationError::RouteNotFound { .. })
        ));
    }

    #[test]
    fn test_external_routes_always_valid() {
        let routes = HashMap::new();
        let validator = RouteValidator::new(routes);

        // External routes are always valid, even if no routes exist
        assert!(validator
            .validate("https://external.com", "en", true)
            .is_ok());
    }

    #[test]
    fn test_validate_batch() {
        let mut routes = HashMap::new();
        routes.insert(
            "en".to_string(),
            ["/", "/about"].iter().map(|s| s.to_string()).collect(),
        );

        let validator = RouteValidator::new(routes);

        let routes_to_validate = vec![
            ("/".to_string(), "en".to_string(), false),
            ("/about".to_string(), "en".to_string(), false),
            ("/no-existe".to_string(), "en".to_string(), false),
        ];

        let result = validator.validate_batch(&routes_to_validate);
        assert!(result.is_err());
        assert_eq!(result.unwrap_err().len(), 1); // Only one invalid route
    }

    #[test]
    fn test_language_mismatch() {
        let mut routes = HashMap::new();
        routes.insert(
            "en".to_string(),
            ["/"].iter().map(|s| s.to_string()).collect(),
        );

        let validator = RouteValidator::new(routes);
        let result = validator.validate("/", "es", false);
        assert!(matches!(
            result,
            Err(RouteValidationError::LanguageNotFound { .. })
        ));
    }
}
