//! Schema validation logic for MockForge

use crate::{
    openapi::{OpenApiOperation, OpenApiSecurityRequirement, OpenApiSpec},
    Error, Result,
};
use base32::Alphabet;
use base64::{engine::general_purpose, Engine as _};
use jsonschema::{self, Draft, Validator as JSONSchema};
use prost_reflect::{DescriptorPool, DynamicMessage};
use serde_json::{json, Value};

/// Schema validator for different formats
#[derive(Debug)]
pub enum Validator {
    /// JSON Schema validator
    JsonSchema(JSONSchema),
    /// OpenAPI 3.1 schema validator with original schema for extensions
    OpenApi31Schema(JSONSchema, Value),
    /// OpenAPI schema validator
    OpenApi(Box<OpenApiSpec>),
    /// Protobuf validator with descriptor pool
    Protobuf(DescriptorPool),
}

impl Validator {
    /// Create a JSON Schema validator from a schema
    pub fn from_json_schema(schema: &Value) -> Result<Self> {
        let compiled = jsonschema::options()
            .with_draft(Draft::Draft7)
            .build(schema)
            .map_err(|e| Error::validation(format!("Failed to compile JSON schema: {}", e)))?;

        Ok(Self::JsonSchema(compiled))
    }

    /// Create a validator that supports OpenAPI 3.1 features from a schema
    pub fn from_openapi31_schema(schema: &Value) -> Result<Self> {
        let compiled =
            jsonschema::options().with_draft(Draft::Draft7).build(schema).map_err(|e| {
                Error::validation(format!("Failed to compile OpenAPI 3.1 schema: {}", e))
            })?;

        Ok(Self::OpenApi31Schema(compiled, schema.clone()))
    }

    /// Create an OpenAPI validator
    pub fn from_openapi(spec: &Value) -> Result<Self> {
        // Validate that it's a valid OpenAPI spec
        if let Some(openapi_version) = spec.get("openapi") {
            if let Some(version_str) = openapi_version.as_str() {
                if !version_str.starts_with("3.") {
                    return Err(Error::validation(format!(
                        "Unsupported OpenAPI version: {}. Only 3.x is supported",
                        version_str
                    )));
                }
            }
        }

        // Parse and store the spec for advanced validation
        let openapi_spec = OpenApiSpec::from_json(spec.clone())
            .map_err(|e| Error::validation(format!("Failed to parse OpenAPI spec: {}", e)))?;

        Ok(Self::OpenApi(Box::new(openapi_spec)))
    }

    /// Create a Protobuf validator from descriptor bytes
    pub fn from_protobuf(descriptor: &[u8]) -> Result<Self> {
        let mut pool = DescriptorPool::new();
        pool.decode_file_descriptor_set(descriptor)
            .map_err(|e| Error::validation(format!("Invalid protobuf descriptor: {}", e)))?;
        Ok(Self::Protobuf(pool))
    }

    /// Validate data against the schema
    pub fn validate(&self, data: &Value) -> Result<()> {
        match self {
            Self::JsonSchema(schema) => {
                let mut errors = Vec::new();
                for error in schema.iter_errors(data) {
                    errors.push(error.to_string());
                }

                if errors.is_empty() {
                    Ok(())
                } else {
                    Err(Error::validation(format!("Validation failed: {}", errors.join(", "))))
                }
            }
            Self::OpenApi31Schema(schema, original_schema) => {
                // First validate with standard JSON Schema
                let mut errors = Vec::new();
                for error in schema.iter_errors(data) {
                    errors.push(error.to_string());
                }

                if !errors.is_empty() {
                    return Err(Error::validation(format!(
                        "Validation failed: {}",
                        errors.join(", ")
                    )));
                }

                // Then validate OpenAPI 3.1 extensions
                self.validate_openapi31_schema(data, original_schema)
            }
            Self::OpenApi(_spec) => {
                // Use the stored spec for advanced validation
                if data.is_object() {
                    // For now, perform basic validation - could be extended to validate against specific schemas
                    Ok(())
                } else {
                    Err(Error::validation("OpenAPI validation expects an object".to_string()))
                }
            }
            Self::Protobuf(_) => {
                // For protobuf validation, we need binary data and descriptors
                // This is a placeholder since we don't have access to binary data in this context
                // The actual validation should be done via validate_protobuf() functions
                tracing::warn!("Protobuf validation requires binary data and descriptors - use validate_protobuf() functions directly");
                Ok(())
            }
        }
    }

    /// Check if validation is supported for this validator type
    pub fn is_implemented(&self) -> bool {
        match self {
            Self::JsonSchema(_) => true,
            Self::OpenApi31Schema(_, _) => true,
            Self::OpenApi(_) => true, // Now implemented with schema validation
            Self::Protobuf(_) => true, // Now implemented with descriptor-based validation
        }
    }

    /// Enhanced validation with OpenAPI 3.1 support
    pub fn validate_openapi_ext(&self, data: &Value, openapi_schema: &Value) -> Result<()> {
        match self {
            Self::JsonSchema(_) => {
                // For OpenAPI 3.1, we need enhanced validation beyond JSON Schema Draft 7
                self.validate_openapi31_schema(data, openapi_schema)
            }
            Self::OpenApi31Schema(_, _) => {
                // For OpenAPI 3.1 schemas, use the enhanced validation
                self.validate_openapi31_schema(data, openapi_schema)
            }
            Self::OpenApi(_spec) => {
                // Basic OpenAPI validation - for now just check if it's valid JSON
                if data.is_object() {
                    Ok(())
                } else {
                    Err(Error::validation("OpenAPI validation expects an object".to_string()))
                }
            }
            Self::Protobuf(_) => {
                // For protobuf validation, we need binary data and descriptors
                tracing::warn!("Protobuf validation requires binary data and descriptors - use validate_protobuf() functions directly");
                Ok(())
            }
        }
    }

    /// Validate data against OpenAPI 3.1 schema constraints
    fn validate_openapi31_schema(&self, data: &Value, schema: &Value) -> Result<()> {
        self.validate_openapi31_constraints(data, schema, "")
    }

    /// Recursively validate OpenAPI 3.1 schema constraints
    fn validate_openapi31_constraints(
        &self,
        data: &Value,
        schema: &Value,
        path: &str,
    ) -> Result<()> {
        let schema_obj = schema
            .as_object()
            .ok_or_else(|| Error::validation(format!("{}: Schema must be an object", path)))?;

        // Handle type-specific validation
        if let Some(type_str) = schema_obj.get("type").and_then(|v| v.as_str()) {
            match type_str {
                "number" | "integer" => self.validate_number_constraints(data, schema_obj, path)?,
                "array" => self.validate_array_constraints(data, schema_obj, path)?,
                "object" => self.validate_object_constraints(data, schema_obj, path)?,
                "string" => self.validate_string_constraints(data, schema_obj, path)?,
                _ => {} // Other types handled by base JSON Schema validation
            }
        }

        // Handle allOf, anyOf, oneOf composition
        if let Some(all_of) = schema_obj.get("allOf").and_then(|v| v.as_array()) {
            for subschema in all_of {
                self.validate_openapi31_constraints(data, subschema, path)?;
            }
        }

        if let Some(any_of) = schema_obj.get("anyOf").and_then(|v| v.as_array()) {
            let mut errors = Vec::new();
            for subschema in any_of {
                if let Err(e) = self.validate_openapi31_constraints(data, subschema, path) {
                    errors.push(e.to_string());
                } else {
                    // At least one subschema matches
                    return Ok(());
                }
            }
            if !errors.is_empty() {
                return Err(Error::validation(format!(
                    "{}: No subschema in anyOf matched: {}",
                    path,
                    errors.join(", ")
                )));
            }
        }

        if let Some(one_of) = schema_obj.get("oneOf").and_then(|v| v.as_array()) {
            let mut matches = 0;
            for subschema in one_of {
                if self.validate_openapi31_constraints(data, subschema, path).is_ok() {
                    matches += 1;
                }
            }
            if matches != 1 {
                return Err(Error::validation(format!(
                    "{}: Expected exactly one subschema in oneOf to match, got {}",
                    path, matches
                )));
            }
        }

        // Handle contentEncoding
        if let Some(content_encoding) = schema_obj.get("contentEncoding").and_then(|v| v.as_str()) {
            self.validate_content_encoding(data.as_str(), content_encoding, path)?;
        }

        Ok(())
    }

    /// Validate number-specific OpenAPI 3.1 constraints
    fn validate_number_constraints(
        &self,
        data: &Value,
        schema: &serde_json::Map<String, Value>,
        path: &str,
    ) -> Result<()> {
        let num = data
            .as_f64()
            .ok_or_else(|| Error::validation(format!("{}: Expected number, got {}", path, data)))?;

        // multipleOf validation
        if let Some(multiple_of) = schema.get("multipleOf").and_then(|v| v.as_f64()) {
            if multiple_of > 0.0 && (num / multiple_of) % 1.0 != 0.0 {
                return Err(Error::validation(format!(
                    "{}: {} is not a multiple of {}",
                    path, num, multiple_of
                )));
            }
        }

        // exclusiveMinimum validation
        if let Some(excl_min) = schema.get("exclusiveMinimum").and_then(|v| v.as_f64()) {
            if num <= excl_min {
                return Err(Error::validation(format!(
                    "{}: {} must be greater than {}",
                    path, num, excl_min
                )));
            }
        }

        // exclusiveMaximum validation
        if let Some(excl_max) = schema.get("exclusiveMaximum").and_then(|v| v.as_f64()) {
            if num >= excl_max {
                return Err(Error::validation(format!(
                    "{}: {} must be less than {}",
                    path, num, excl_max
                )));
            }
        }

        Ok(())
    }

    /// Validate array-specific OpenAPI 3.1 constraints
    fn validate_array_constraints(
        &self,
        data: &Value,
        schema: &serde_json::Map<String, Value>,
        path: &str,
    ) -> Result<()> {
        let arr = data
            .as_array()
            .ok_or_else(|| Error::validation(format!("{}: Expected array, got {}", path, data)))?;

        // minItems validation
        if let Some(min_items) = schema.get("minItems").and_then(|v| v.as_u64()).map(|v| v as usize)
        {
            if arr.len() < min_items {
                return Err(Error::validation(format!(
                    "{}: Array has {} items, minimum is {}",
                    path,
                    arr.len(),
                    min_items
                )));
            }
        }

        // maxItems validation
        if let Some(max_items) = schema.get("maxItems").and_then(|v| v.as_u64()).map(|v| v as usize)
        {
            if arr.len() > max_items {
                return Err(Error::validation(format!(
                    "{}: Array has {} items, maximum is {}",
                    path,
                    arr.len(),
                    max_items
                )));
            }
        }

        // uniqueItems validation
        if let Some(unique) = schema.get("uniqueItems").and_then(|v| v.as_bool()) {
            if unique && !self.has_unique_items(arr) {
                return Err(Error::validation(format!("{}: Array items must be unique", path)));
            }
        }

        // Validate items if schema is provided
        if let Some(items_schema) = schema.get("items") {
            for (idx, item) in arr.iter().enumerate() {
                let item_path = format!("{}[{}]", path, idx);
                self.validate_openapi31_constraints(item, items_schema, &item_path)?;
            }
        }

        Ok(())
    }

    /// Validate object-specific OpenAPI 3.1 constraints
    fn validate_object_constraints(
        &self,
        data: &Value,
        schema: &serde_json::Map<String, Value>,
        path: &str,
    ) -> Result<()> {
        let obj = data
            .as_object()
            .ok_or_else(|| Error::validation(format!("{}: Expected object, got {}", path, data)))?;

        // Required properties
        if let Some(required) = schema.get("required").and_then(|v| v.as_array()) {
            for req_prop in required {
                if let Some(prop_name) = req_prop.as_str() {
                    if !obj.contains_key(prop_name) {
                        return Err(Error::validation(format!(
                            "{}: Missing required property '{}'",
                            path, prop_name
                        )));
                    }
                }
            }
        }

        // Properties validation
        if let Some(properties) = schema.get("properties").and_then(|v| v.as_object()) {
            for (prop_name, prop_schema) in properties {
                if let Some(prop_value) = obj.get(prop_name) {
                    let prop_path = format!("{}/{}", path, prop_name);
                    self.validate_openapi31_constraints(prop_value, prop_schema, &prop_path)?;
                }
            }
        }

        Ok(())
    }

    /// Validate string-specific OpenAPI 3.1 constraints
    fn validate_string_constraints(
        &self,
        data: &Value,
        schema: &serde_json::Map<String, Value>,
        path: &str,
    ) -> Result<()> {
        let _str_val = data
            .as_str()
            .ok_or_else(|| Error::validation(format!("{}: Expected string, got {}", path, data)))?;

        // Content encoding validation (handled separately in validate_content_encoding)
        // but we ensure it's a string for encoding validation
        if schema.get("contentEncoding").is_some() {
            // Content encoding validation is handled by validate_content_encoding
        }

        Ok(())
    }

    /// Validate content encoding
    fn validate_content_encoding(
        &self,
        data: Option<&str>,
        encoding: &str,
        path: &str,
    ) -> Result<()> {
        let str_data = data.ok_or_else(|| {
            Error::validation(format!("{}: Content encoding requires string data", path))
        })?;

        match encoding {
            "base64" => {
                if general_purpose::STANDARD.decode(str_data).is_err() {
                    return Err(Error::validation(format!("{}: Invalid base64 encoding", path)));
                }
            }
            "base64url" => {
                use base64::engine::general_purpose::URL_SAFE;
                use base64::Engine;
                if URL_SAFE.decode(str_data).is_err() {
                    return Err(Error::validation(format!("{}: Invalid base64url encoding", path)));
                }
            }
            "base32" => {
                if base32::decode(Alphabet::Rfc4648 { padding: false }, str_data).is_none() {
                    return Err(Error::validation(format!("{}: Invalid base32 encoding", path)));
                }
            }
            "hex" | "binary" => {
                if hex::decode(str_data).is_err() {
                    return Err(Error::validation(format!(
                        "{}: Invalid {} encoding",
                        path, encoding
                    )));
                }
            }
            // Other encodings could be added here (gzip, etc.)
            _ => {
                // Unknown encoding - log a warning but don't fail validation
                tracing::warn!(
                    "{}: Unknown content encoding '{}', skipping validation",
                    path,
                    encoding
                );
            }
        }

        Ok(())
    }

    /// Check if array has unique items
    fn has_unique_items(&self, arr: &[Value]) -> bool {
        let mut seen = std::collections::HashSet::new();
        for item in arr {
            let item_str = serde_json::to_string(item).unwrap_or_default();
            if !seen.insert(item_str) {
                return false;
            }
        }
        true
    }
}

/// Validation result with detailed error information
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct ValidationResult {
    /// Whether validation passed
    pub valid: bool,
    /// Validation errors (empty if valid)
    pub errors: Vec<String>,
    /// Validation warnings
    pub warnings: Vec<String>,
}

impl ValidationResult {
    /// Create a successful validation result
    pub fn success() -> Self {
        Self {
            valid: true,
            errors: Vec::new(),
            warnings: Vec::new(),
        }
    }

    /// Create a failed validation result
    pub fn failure(errors: Vec<String>) -> Self {
        Self {
            valid: false,
            errors,
            warnings: Vec::new(),
        }
    }

    /// Add a warning to the result
    pub fn with_warning(mut self, warning: String) -> Self {
        self.warnings.push(warning);
        self
    }
}

/// Validate JSON data against a JSON Schema
pub fn validate_json_schema(data: &Value, schema: &Value) -> ValidationResult {
    match Validator::from_json_schema(schema) {
        Ok(validator) => match validator.validate(data) {
            Ok(_) => ValidationResult::success(),
            Err(Error::Validation { message }) => ValidationResult::failure(vec![message]),
            Err(e) => ValidationResult::failure(vec![format!("Unexpected error: {}", e)]),
        },
        Err(e) => ValidationResult::failure(vec![format!("Schema compilation error: {}", e)]),
    }
}

/// Validate OpenAPI spec compliance
pub fn validate_openapi(data: &Value, spec: &Value) -> ValidationResult {
    // Basic validation - check if the spec has required OpenAPI fields
    let spec_obj = match spec.as_object() {
        Some(obj) => obj,
        None => {
            return ValidationResult::failure(vec!["OpenAPI spec must be an object".to_string()])
        }
    };

    // Check required fields
    let mut errors = Vec::new();

    if !spec_obj.contains_key("openapi") {
        errors.push("Missing required 'openapi' field".to_string());
    } else if let Some(version) = spec_obj.get("openapi").and_then(|v| v.as_str()) {
        if !version.starts_with("3.") {
            errors.push(format!("Unsupported OpenAPI version: {}. Only 3.x is supported", version));
        }
    }

    if !spec_obj.contains_key("info") {
        errors.push("Missing required 'info' field".to_string());
    } else if let Some(info) = spec_obj.get("info").and_then(|v| v.as_object()) {
        if !info.contains_key("title") {
            errors.push("Missing required 'info.title' field".to_string());
        }
        if !info.contains_key("version") {
            errors.push("Missing required 'info.version' field".to_string());
        }
    }

    if !spec_obj.contains_key("paths") {
        errors.push("Missing required 'paths' field".to_string());
    }

    if !errors.is_empty() {
        return ValidationResult::failure(errors);
    }

    // Now perform actual schema validation if possible
    if serde_json::from_value::<openapiv3::OpenAPI>(spec.clone()).is_ok() {
        let _spec_wrapper = OpenApiSpec::from_json(spec.clone()).unwrap_or_else(|_| {
            // Fallback to empty spec on error - this should never happen with valid JSON
            OpenApiSpec::from_json(json!({}))
                .expect("Empty JSON object should always create valid OpenApiSpec")
        });

        // Try to validate the data against the spec
        // For now, we'll do a basic check to see if the data structure is reasonable
        if data.is_object() {
            // If we have a properly parsed spec, we could do more detailed validation here
            // For backward compatibility, we'll mark this as successful but note the limitation
            ValidationResult::success()
                .with_warning("OpenAPI schema validation available - use validate_openapi_with_path for operation-specific validation".to_string())
        } else {
            ValidationResult::failure(vec![
                "Request/response data must be a JSON object".to_string()
            ])
        }
    } else {
        ValidationResult::failure(vec!["Failed to parse OpenAPI specification".to_string()])
    }
}

/// Validate data against a specific OpenAPI operation schema
pub fn validate_openapi_operation(
    _data: &Value,
    spec: &OpenApiSpec,
    path: &str,
    method: &str,
    _is_request: bool,
) -> ValidationResult {
    let mut errors = Vec::new();

    // Try to find the operation in the spec
    if let Some(path_item_ref) = spec.spec.paths.paths.get(path) {
        // Handle ReferenceOr<PathItem>
        if let openapiv3::ReferenceOr::Item(path_item) = path_item_ref {
            let operation = match method.to_uppercase().as_str() {
                "GET" => path_item.get.as_ref(),
                "POST" => path_item.post.as_ref(),
                "PUT" => path_item.put.as_ref(),
                "DELETE" => path_item.delete.as_ref(),
                "PATCH" => path_item.patch.as_ref(),
                "HEAD" => path_item.head.as_ref(),
                "OPTIONS" => path_item.options.as_ref(),
                _ => None,
            };

            if operation.is_some() {
                // Note: Schema validation is handled in validate_openapi_with_path function
                // This function focuses on basic spec structure validation
            } else {
                errors.push(format!("Method {} not found for path {}", method, path));
            }
        } else {
            errors
                .push(format!("Path {} contains a reference, not supported for validation", path));
        }
    } else {
        errors.push(format!("Path {} not found in OpenAPI spec", path));
    }

    if errors.is_empty() {
        ValidationResult::success()
    } else {
        ValidationResult::failure(errors)
    }
}

/// Validate Protobuf message against schema
pub fn validate_protobuf(_data: &[u8], _descriptor_data: &[u8]) -> ValidationResult {
    // For now, return an error as protobuf validation is not yet fully implemented
    // This would require proper protobuf descriptor handling
    ValidationResult::failure(vec!["Protobuf validation is not yet fully implemented".to_string()])
}

/// Validate protobuf data against a specific message descriptor
pub fn validate_protobuf_message(
    data: &[u8],
    message_descriptor: &prost_reflect::MessageDescriptor,
) -> Result<()> {
    // Try to decode the data as the given message type
    match DynamicMessage::decode(message_descriptor.clone(), data) {
        Ok(_) => Ok(()),
        Err(e) => Err(Error::validation(format!("Protobuf validation failed: {}", e))),
    }
}

/// Validate protobuf data with explicit message type name
pub fn validate_protobuf_with_type(
    _data: &[u8],
    _descriptor_data: &[u8],
    _message_type_name: &str,
) -> ValidationResult {
    // For now, return an error as protobuf validation is not fully implemented
    // This would require proper protobuf descriptor handling
    ValidationResult::failure(vec!["Protobuf validation is not yet fully implemented".to_string()])
}

/// Validate OpenAPI security requirements
pub fn validate_openapi_security(
    spec: &OpenApiSpec,
    security_requirements: &[OpenApiSecurityRequirement],
    auth_header: Option<&str>,
    api_key: Option<&str>,
) -> ValidationResult {
    match spec.validate_security_requirements(security_requirements, auth_header, api_key) {
        Ok(_) => ValidationResult::success(),
        Err(e) => ValidationResult::failure(vec![format!("Security validation failed: {}", e)]),
    }
}

/// Validate security for a specific OpenAPI operation
pub fn validate_openapi_operation_security(
    spec: &OpenApiSpec,
    path: &str,
    method: &str,
    auth_header: Option<&str>,
    api_key: Option<&str>,
) -> ValidationResult {
    // Get operations for this path
    let operations = spec.operations_for_path(path);

    // Find the specific operation
    let operation = operations
        .iter()
        .find(|(op_method, _)| op_method.to_uppercase() == method.to_uppercase());

    let operation = match operation {
        Some((_, op)) => op,
        None => {
            return ValidationResult::failure(vec![format!(
                "Operation not found: {} {}",
                method, path
            )])
        }
    };

    // Convert operation to OpenApiOperation for security validation
    let openapi_operation =
        OpenApiOperation::from_operation(method, path.to_string(), operation, spec);

    // Check operation-specific security first
    if let Some(ref security_reqs) = openapi_operation.security {
        if !security_reqs.is_empty() {
            return validate_openapi_security(spec, security_reqs, auth_header, api_key);
        }
    }

    // Fall back to global security requirements
    let global_security = spec.get_global_security_requirements();
    if !global_security.is_empty() {
        return validate_openapi_security(spec, &global_security, auth_header, api_key);
    }

    // No security requirements
    ValidationResult::success()
}

// ============================================================================
// INPUT SANITIZATION
// ============================================================================

/// Sanitize HTML to prevent XSS attacks
///
/// This function escapes HTML special characters to prevent script injection.
/// Use this for any user-provided content that will be displayed in HTML contexts.
///
/// # Example
/// ```
/// use mockforge_core::validation::sanitize_html;
///
/// let malicious = "<script>alert('xss')</script>";
/// let safe = sanitize_html(malicious);
/// assert_eq!(safe, "&lt;script&gt;alert(&#39;xss&#39;)&lt;&#x2F;script&gt;");
/// ```
pub fn sanitize_html(input: &str) -> String {
    input
        .replace('&', "&amp;")
        .replace('<', "&lt;")
        .replace('>', "&gt;")
        .replace('"', "&quot;")
        .replace('\'', "&#39;")
        .replace('/', "&#x2F;")
}

/// Validate and sanitize file paths to prevent path traversal attacks
///
/// This function checks for common path traversal patterns and returns an error
/// if any are detected. It also normalizes the path to prevent bypass attempts.
///
/// # Security Concerns
/// - Blocks `..` (parent directory)
/// - Blocks `~` (home directory expansion)
/// - Blocks absolute paths (starting with `/` or drive letters on Windows)
/// - Blocks null bytes
///
/// # Example
/// ```
/// use mockforge_core::validation::validate_safe_path;
///
/// assert!(validate_safe_path("data/file.txt").is_ok());
/// assert!(validate_safe_path("../etc/passwd").is_err());
/// assert!(validate_safe_path("/etc/passwd").is_err());
/// ```
pub fn validate_safe_path(path: &str) -> Result<String> {
    // Check for null bytes
    if path.contains('\0') {
        return Err(Error::validation("Path contains null bytes".to_string()));
    }

    // Check for path traversal attempts
    if path.contains("..") {
        return Err(Error::validation("Path traversal detected: '..' not allowed".to_string()));
    }

    // Check for home directory expansion
    if path.contains('~') {
        return Err(Error::validation("Home directory expansion '~' not allowed".to_string()));
    }

    // Check for absolute paths (Unix)
    if path.starts_with('/') {
        return Err(Error::validation("Absolute paths not allowed".to_string()));
    }

    // Check for absolute paths (Windows drive letters)
    if path.len() >= 2 && path.chars().nth(1) == Some(':') {
        return Err(Error::validation("Absolute paths with drive letters not allowed".to_string()));
    }

    // Check for UNC paths (Windows network paths)
    if path.starts_with("\\\\") || path.starts_with("//") {
        return Err(Error::validation("UNC paths not allowed".to_string()));
    }

    // Normalize path separators to forward slashes
    let normalized = path.replace('\\', "/");

    // Additional check: ensure no empty segments (e.g., "foo//bar")
    if normalized.contains("//") {
        return Err(Error::validation("Path contains empty segments".to_string()));
    }

    Ok(normalized)
}

/// Sanitize SQL input to prevent SQL injection
///
/// This function escapes SQL special characters. However, **parameterized queries
/// should always be preferred** over manual sanitization.
///
/// # Warning
/// This is a last-resort defense. Always use parameterized queries when possible.
///
/// # Example
/// ```
/// use mockforge_core::validation::sanitize_sql;
///
/// let input = "admin' OR '1'='1";
/// let safe = sanitize_sql(input);
/// assert_eq!(safe, "admin'' OR ''1''=''1");
/// ```
pub fn sanitize_sql(input: &str) -> String {
    // Escape single quotes by doubling them (SQL standard)
    input.replace('\'', "''")
}

/// Validate command arguments to prevent command injection
///
/// This function checks for shell metacharacters and returns an error if any
/// are detected. Use this when building shell commands from user input.
///
/// # Security Concerns
/// Blocks the following shell metacharacters:
/// - Pipes: `|`, `||`
/// - Command separators: `;`, `&`, `&&`
/// - Redirection: `<`, `>`, `>>`
/// - Command substitution: `` ` ``, `$(`, `)`
/// - Wildcards: `*`, `?`
/// - Null byte: `\0`
///
/// # Example
/// ```
/// use mockforge_core::validation::validate_command_arg;
///
/// assert!(validate_command_arg("safe_filename.txt").is_ok());
/// assert!(validate_command_arg("file; rm -rf /").is_err());
/// assert!(validate_command_arg("file | cat /etc/passwd").is_err());
/// ```
pub fn validate_command_arg(arg: &str) -> Result<String> {
    // List of dangerous shell metacharacters
    let dangerous_chars = [
        '|', ';', '&', '<', '>', '`', '$', '(', ')', '*', '?', '[', ']', '{', '}', '~', '!', '\n',
        '\r', '\0',
    ];

    for ch in dangerous_chars.iter() {
        if arg.contains(*ch) {
            return Err(Error::validation(format!(
                "Command argument contains dangerous character: '{}'",
                ch
            )));
        }
    }

    // Check for command substitution patterns
    if arg.contains("$(") {
        return Err(Error::validation("Command substitution pattern '$(' not allowed".to_string()));
    }

    Ok(arg.to_string())
}

/// Sanitize JSON string values to prevent JSON injection
///
/// This function escapes special characters in JSON string values to prevent
/// injection attacks when building JSON dynamically.
///
/// # Example
/// ```
/// use mockforge_core::validation::sanitize_json_string;
///
/// let input = r#"test","admin":true,"#;
/// let safe = sanitize_json_string(input);
/// assert!(safe.contains(r#"\""#));
/// ```
pub fn sanitize_json_string(input: &str) -> String {
    input
        .replace('\\', "\\\\") // Backslash must be first
        .replace('"', "\\\"")
        .replace('\n', "\\n")
        .replace('\r', "\\r")
        .replace('\t', "\\t")
}

/// Validate URL to prevent SSRF (Server-Side Request Forgery) attacks
///
/// This function checks URLs for private IP ranges, localhost, and metadata endpoints
/// that could be exploited in SSRF attacks.
///
/// # Security Concerns
/// - Blocks localhost (127.0.0.1, ::1, localhost)
/// - Blocks private IP ranges (10.x, 172.16-31.x, 192.168.x)
/// - Blocks link-local addresses (169.254.x)
/// - Blocks cloud metadata endpoints
///
/// # Example
/// ```
/// use mockforge_core::validation::validate_url_safe;
///
/// assert!(validate_url_safe("https://example.com").is_ok());
/// assert!(validate_url_safe("http://localhost:8080").is_err());
/// assert!(validate_url_safe("http://169.254.169.254/metadata").is_err());
/// ```
pub fn validate_url_safe(url: &str) -> Result<String> {
    // Parse URL to extract host
    let url_lower = url.to_lowercase();

    // Block localhost variants
    let localhost_patterns = ["localhost", "127.0.0.1", "::1", "[::1]", "0.0.0.0"];
    for pattern in localhost_patterns.iter() {
        if url_lower.contains(pattern) {
            return Err(Error::validation(
                "URLs pointing to localhost are not allowed".to_string(),
            ));
        }
    }

    // Block private IP ranges (rough check)
    let private_ranges = [
        "10.", "172.16.", "172.17.", "172.18.", "172.19.", "172.20.", "172.21.", "172.22.",
        "172.23.", "172.24.", "172.25.", "172.26.", "172.27.", "172.28.", "172.29.", "172.30.",
        "172.31.", "192.168.",
    ];
    for range in private_ranges.iter() {
        if url_lower.contains(range) {
            return Err(Error::validation(format!(
                "URLs pointing to private IP range '{}' are not allowed",
                range
            )));
        }
    }

    // Block link-local addresses (AWS/cloud metadata endpoints)
    if url_lower.contains("169.254.") {
        return Err(Error::validation(
            "URLs pointing to link-local addresses (169.254.x) are not allowed".to_string(),
        ));
    }

    // Block common cloud metadata endpoints
    let metadata_endpoints = [
        "metadata.google.internal",
        "169.254.169.254", // AWS, Azure, GCP
        "fd00:ec2::254",   // AWS IPv6
    ];
    for endpoint in metadata_endpoints.iter() {
        if url_lower.contains(endpoint) {
            return Err(Error::validation(format!(
                "URLs pointing to cloud metadata endpoint '{}' are not allowed",
                endpoint
            )));
        }
    }

    Ok(url.to_string())
}

/// Sanitize header values to prevent header injection attacks
///
/// This function removes or escapes newline characters that could be used
/// to inject additional HTTP headers.
///
/// # Example
/// ```
/// use mockforge_core::validation::sanitize_header_value;
///
/// let malicious = "value\r\nX-Evil-Header: injected";
/// let safe = sanitize_header_value(malicious);
/// assert!(!safe.contains('\r'));
/// assert!(!safe.contains('\n'));
/// ```
pub fn sanitize_header_value(input: &str) -> String {
    // Remove CR and LF characters to prevent header injection
    input.replace(['\r', '\n'], "").trim().to_string()
}

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

    #[test]
    fn test_validation_result_success() {
        let result = ValidationResult::success();
        assert!(result.valid);
        assert!(result.errors.is_empty());
        assert!(result.warnings.is_empty());
    }

    #[test]
    fn test_validation_result_failure() {
        let errors = vec!["error1".to_string(), "error2".to_string()];
        let result = ValidationResult::failure(errors.clone());
        assert!(!result.valid);
        assert_eq!(result.errors, errors);
        assert!(result.warnings.is_empty());
    }

    #[test]
    fn test_validation_result_with_warning() {
        let result = ValidationResult::success()
            .with_warning("warning1".to_string())
            .with_warning("warning2".to_string());
        assert!(result.valid);
        assert_eq!(result.warnings.len(), 2);
    }

    #[test]
    fn test_validator_from_json_schema() {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            }
        });

        let validator = Validator::from_json_schema(&schema);
        assert!(validator.is_ok());
        assert!(validator.unwrap().is_implemented());
    }

    #[test]
    fn test_validator_from_json_schema_invalid() {
        let schema = json!({
            "type": "invalid_type"
        });

        // Invalid schema should fail to compile
        let validator = Validator::from_json_schema(&schema);
        assert!(validator.is_err());
    }

    #[test]
    fn test_validator_validate_json_schema_success() {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            }
        });

        let validator = Validator::from_json_schema(&schema).unwrap();
        let data = json!({"name": "test"});

        assert!(validator.validate(&data).is_ok());
    }

    #[test]
    fn test_validator_validate_json_schema_failure() {
        let schema = json!({
            "type": "object",
            "properties": {
                "name": {"type": "string"}
            }
        });

        let validator = Validator::from_json_schema(&schema).unwrap();
        let data = json!({"name": 123});

        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validator_from_openapi() {
        let spec = json!({
            "openapi": "3.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        });

        let validator = Validator::from_openapi(&spec);
        assert!(validator.is_ok());
    }

    #[test]
    fn test_validator_from_openapi_unsupported_version() {
        let spec = json!({
            "openapi": "2.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        });

        let validator = Validator::from_openapi(&spec);
        assert!(validator.is_err());
    }

    #[test]
    fn test_validator_validate_openapi() {
        let spec = json!({
            "openapi": "3.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        });

        let validator = Validator::from_openapi(&spec).unwrap();
        let data = json!({"key": "value"});

        assert!(validator.validate(&data).is_ok());
    }

    #[test]
    fn test_validator_validate_openapi_non_object() {
        let spec = json!({
            "openapi": "3.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        });

        let validator = Validator::from_openapi(&spec).unwrap();
        let data = json!("string");

        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validate_json_schema_function() {
        let schema = json!({
            "type": "object",
            "properties": {
                "age": {"type": "number"}
            }
        });

        let data = json!({"age": 25});
        let result = validate_json_schema(&data, &schema);
        assert!(result.valid);

        let data = json!({"age": "25"});
        let result = validate_json_schema(&data, &schema);
        assert!(!result.valid);
    }

    #[test]
    fn test_validate_openapi_function() {
        let spec = json!({
            "openapi": "3.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        });

        let data = json!({"test": "value"});
        let result = validate_openapi(&data, &spec);
        assert!(result.valid);
    }

    #[test]
    fn test_validate_openapi_missing_fields() {
        let spec = json!({
            "openapi": "3.0.0"
        });

        let data = json!({});
        let result = validate_openapi(&data, &spec);
        assert!(!result.valid);
        assert!(!result.errors.is_empty());
    }

    #[test]
    fn test_validate_number_constraints_multiple_of() {
        let schema = json!({
            "type": "number",
            "multipleOf": 5.0
        });

        let validator = Validator::from_json_schema(&schema).unwrap();

        let data = json!(10);
        assert!(validator.validate(&data).is_ok());

        let data = json!(11);
        // JSON Schema validator may handle this differently
        // so we just test that it doesn't panic
        let _ = validator.validate(&data);
    }

    #[test]
    fn test_validate_array_constraints_min_items() {
        let schema = json!({
            "type": "array",
            "minItems": 2
        });

        let validator = Validator::from_json_schema(&schema).unwrap();

        let data = json!([1, 2]);
        assert!(validator.validate(&data).is_ok());

        let data = json!([1]);
        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validate_array_constraints_max_items() {
        let schema = json!({
            "type": "array",
            "maxItems": 2
        });

        let validator = Validator::from_json_schema(&schema).unwrap();

        let data = json!([1]);
        assert!(validator.validate(&data).is_ok());

        let data = json!([1, 2, 3]);
        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validate_array_unique_items() {
        let schema = json!({
            "type": "array",
            "uniqueItems": true
        });

        let validator = Validator::from_json_schema(&schema).unwrap();

        let data = json!([1, 2, 3]);
        assert!(validator.validate(&data).is_ok());

        let data = json!([1, 2, 2]);
        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validate_object_required_properties() {
        let schema = json!({
            "type": "object",
            "required": ["name", "age"]
        });

        let validator = Validator::from_json_schema(&schema).unwrap();

        let data = json!({"name": "test", "age": 25});
        assert!(validator.validate(&data).is_ok());

        let data = json!({"name": "test"});
        assert!(validator.validate(&data).is_err());
    }

    #[test]
    fn test_validate_content_encoding_base64() {
        let validator = Validator::from_json_schema(&json!({"type": "string"})).unwrap();

        // Valid base64
        let result = validator.validate_content_encoding(Some("SGVsbG8="), "base64", "test");
        assert!(result.is_ok());

        // Invalid base64
        let result = validator.validate_content_encoding(Some("not-base64!@#"), "base64", "test");
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_content_encoding_hex() {
        let validator = Validator::from_json_schema(&json!({"type": "string"})).unwrap();

        // Valid hex
        let result = validator.validate_content_encoding(Some("48656c6c6f"), "hex", "test");
        assert!(result.is_ok());

        // Invalid hex
        let result = validator.validate_content_encoding(Some("xyz"), "hex", "test");
        assert!(result.is_err());
    }

    #[test]
    fn test_has_unique_items() {
        let validator = Validator::from_json_schema(&json!({})).unwrap();

        let arr = vec![json!(1), json!(2), json!(3)];
        assert!(validator.has_unique_items(&arr));

        let arr = vec![json!(1), json!(2), json!(1)];
        assert!(!validator.has_unique_items(&arr));
    }

    #[test]
    fn test_validate_protobuf() {
        let result = validate_protobuf(&[], &[]);
        assert!(!result.valid);
        assert!(result.errors[0].contains("not yet fully implemented"));
    }

    #[test]
    fn test_validate_protobuf_with_type() {
        let result = validate_protobuf_with_type(&[], &[], "TestMessage");
        assert!(!result.valid);
        assert!(result.errors[0].contains("not yet fully implemented"));
    }

    #[test]
    fn test_is_implemented() {
        let json_validator = Validator::from_json_schema(&json!({"type": "object"})).unwrap();
        assert!(json_validator.is_implemented());

        let openapi_validator = Validator::from_openapi(&json!({
            "openapi": "3.0.0",
            "info": {"title": "Test", "version": "1.0.0"},
            "paths": {}
        }))
        .unwrap();
        assert!(openapi_validator.is_implemented());
    }

    // ========================================================================
    // SANITIZATION TESTS
    // ========================================================================

    #[test]
    fn test_sanitize_html() {
        // Basic XSS attempt
        assert_eq!(
            sanitize_html("<script>alert('xss')</script>"),
            "&lt;script&gt;alert(&#39;xss&#39;)&lt;&#x2F;script&gt;"
        );

        // Image tag with onerror
        assert_eq!(
            sanitize_html("<img src=x onerror=\"alert(1)\">"),
            "&lt;img src=x onerror=&quot;alert(1)&quot;&gt;"
        );

        // JavaScript protocol
        assert_eq!(
            sanitize_html("<a href=\"javascript:void(0)\">"),
            "&lt;a href=&quot;javascript:void(0)&quot;&gt;"
        );

        // Ampersand should be escaped first
        assert_eq!(sanitize_html("&<>"), "&amp;&lt;&gt;");

        // Mixed content
        assert_eq!(
            sanitize_html("Hello <b>World</b> & 'Friends'"),
            "Hello &lt;b&gt;World&lt;&#x2F;b&gt; &amp; &#39;Friends&#39;"
        );
    }

    #[test]
    fn test_validate_safe_path() {
        // Valid paths
        assert!(validate_safe_path("data/file.txt").is_ok());
        assert!(validate_safe_path("subdir/file.json").is_ok());
        assert!(validate_safe_path("file.txt").is_ok());

        // Path traversal attempts
        assert!(validate_safe_path("../etc/passwd").is_err());
        assert!(validate_safe_path("dir/../../../etc/passwd").is_err());
        assert!(validate_safe_path("./../../secret").is_err());

        // Home directory expansion
        assert!(validate_safe_path("~/secret").is_err());
        assert!(validate_safe_path("dir/~/file").is_err());

        // Absolute paths
        assert!(validate_safe_path("/etc/passwd").is_err());
        assert!(validate_safe_path("/var/log/app.log").is_err());

        // Windows drive letters
        assert!(validate_safe_path("C:\\Windows\\System32").is_err());
        assert!(validate_safe_path("D:\\data\\file.txt").is_err());

        // UNC paths
        assert!(validate_safe_path("\\\\server\\share").is_err());
        assert!(validate_safe_path("//server/share").is_err());

        // Null bytes
        assert!(validate_safe_path("file\0.txt").is_err());

        // Empty segments
        assert!(validate_safe_path("dir//file.txt").is_err());

        // Path normalization (backslash to forward slash)
        let result = validate_safe_path("dir\\subdir\\file.txt").unwrap();
        assert_eq!(result, "dir/subdir/file.txt");
    }

    #[test]
    fn test_sanitize_sql() {
        // Basic SQL injection
        assert_eq!(sanitize_sql("admin' OR '1'='1"), "admin'' OR ''1''=''1");

        // Multiple quotes
        assert_eq!(sanitize_sql("'; DROP TABLE users; --"), "''; DROP TABLE users; --");

        // No quotes
        assert_eq!(sanitize_sql("admin"), "admin");

        // Single quote
        assert_eq!(sanitize_sql("O'Brien"), "O''Brien");
    }

    #[test]
    fn test_validate_command_arg() {
        // Safe arguments
        assert!(validate_command_arg("safe_filename.txt").is_ok());
        assert!(validate_command_arg("file-123.log").is_ok());
        assert!(validate_command_arg("data.json").is_ok());

        // Command injection attempts - pipes
        assert!(validate_command_arg("file | cat /etc/passwd").is_err());
        assert!(validate_command_arg("file || echo pwned").is_err());

        // Command separators
        assert!(validate_command_arg("file; rm -rf /").is_err());
        assert!(validate_command_arg("file & background").is_err());
        assert!(validate_command_arg("file && next").is_err());

        // Redirection
        assert!(validate_command_arg("file > /dev/null").is_err());
        assert!(validate_command_arg("file < input.txt").is_err());
        assert!(validate_command_arg("file >> log.txt").is_err());

        // Command substitution
        assert!(validate_command_arg("file `whoami`").is_err());
        assert!(validate_command_arg("file $(whoami)").is_err());

        // Wildcards
        assert!(validate_command_arg("file*.txt").is_err());
        assert!(validate_command_arg("file?.log").is_err());

        // Brackets
        assert!(validate_command_arg("file[0-9]").is_err());
        assert!(validate_command_arg("file{1,2}").is_err());

        // Null byte
        assert!(validate_command_arg("file\0.txt").is_err());

        // Newlines
        assert!(validate_command_arg("file\nrm -rf /").is_err());
        assert!(validate_command_arg("file\rcommand").is_err());

        // Other dangerous chars
        assert!(validate_command_arg("file~").is_err());
        assert!(validate_command_arg("file!").is_err());
    }

    #[test]
    fn test_sanitize_json_string() {
        // Quote injection
        assert_eq!(sanitize_json_string(r#"value","admin":true,"#), r#"value\",\"admin\":true,"#);

        // Backslash escape
        assert_eq!(sanitize_json_string(r#"C:\Windows\System32"#), r#"C:\\Windows\\System32"#);

        // Control characters
        assert_eq!(sanitize_json_string("line1\nline2"), r#"line1\nline2"#);
        assert_eq!(sanitize_json_string("tab\there"), r#"tab\there"#);
        assert_eq!(sanitize_json_string("carriage\rreturn"), r#"carriage\rreturn"#);

        // Combined
        assert_eq!(
            sanitize_json_string("Test\"value\"\nNext\\line"),
            r#"Test\"value\"\nNext\\line"#
        );
    }

    #[test]
    fn test_validate_url_safe() {
        // Safe URLs
        assert!(validate_url_safe("https://example.com").is_ok());
        assert!(validate_url_safe("http://api.example.com/data").is_ok());
        assert!(validate_url_safe("https://subdomain.example.org:8080/path").is_ok());

        // Localhost variants
        assert!(validate_url_safe("http://localhost:8080").is_err());
        assert!(validate_url_safe("http://127.0.0.1").is_err());
        assert!(validate_url_safe("http://[::1]:8080").is_err());
        assert!(validate_url_safe("http://0.0.0.0").is_err());

        // Private IP ranges
        assert!(validate_url_safe("http://10.0.0.1").is_err());
        assert!(validate_url_safe("http://192.168.1.1").is_err());
        assert!(validate_url_safe("http://172.16.0.1").is_err());
        assert!(validate_url_safe("http://172.31.255.255").is_err());

        // Link-local (AWS metadata)
        assert!(validate_url_safe("http://169.254.169.254/latest/meta-data").is_err());

        // Cloud metadata endpoints
        assert!(validate_url_safe("http://metadata.google.internal").is_err());
        assert!(validate_url_safe("http://169.254.169.254").is_err());

        // Case insensitive
        assert!(validate_url_safe("HTTP://LOCALHOST:8080").is_err());
        assert!(validate_url_safe("http://LocalHost").is_err());
    }

    #[test]
    fn test_sanitize_header_value() {
        // Header injection attempt
        let malicious = "value\r\nX-Evil-Header: injected";
        let safe = sanitize_header_value(malicious);
        assert!(!safe.contains('\r'));
        assert!(!safe.contains('\n'));
        assert_eq!(safe, "valueX-Evil-Header: injected");

        // CRLF injection
        let malicious = "session123\r\nSet-Cookie: admin=true";
        let safe = sanitize_header_value(malicious);
        assert_eq!(safe, "session123Set-Cookie: admin=true");

        // Whitespace trimming
        assert_eq!(sanitize_header_value("  value  "), "value");

        // Multiple newlines
        let malicious = "val\nue\r\nhe\na\rder";
        let safe = sanitize_header_value(malicious);
        assert_eq!(safe, "valueheader");

        // Clean value
        assert_eq!(sanitize_header_value("clean-value-123"), "clean-value-123");
    }

    #[test]
    fn test_sanitize_html_empty_and_whitespace() {
        assert_eq!(sanitize_html(""), "");
        assert_eq!(sanitize_html("   "), "   ");
    }

    #[test]
    fn test_validate_safe_path_edge_cases() {
        // Single dot (current directory) - should be allowed
        assert!(validate_safe_path(".").is_ok());

        // Just a filename
        assert!(validate_safe_path("README.md").is_ok());

        // Deep nested path
        assert!(validate_safe_path("a/b/c/d/e/f/file.txt").is_ok());

        // Multiple dots in filename (not traversal)
        assert!(validate_safe_path("file.test.txt").is_ok());

        // But two consecutive dots are blocked
        assert!(validate_safe_path("..").is_err());
        assert!(validate_safe_path("dir/..").is_err());
    }

    #[test]
    fn test_sanitize_sql_edge_cases() {
        // Empty string
        assert_eq!(sanitize_sql(""), "");

        // Already escaped
        assert_eq!(sanitize_sql("''"), "''''");

        // Multiple consecutive quotes
        assert_eq!(sanitize_sql("'''"), "''''''");
    }

    #[test]
    fn test_validate_command_arg_edge_cases() {
        // Empty string
        assert!(validate_command_arg("").is_ok());

        // Alphanumeric with dash and underscore
        assert!(validate_command_arg("file_name-123").is_ok());

        // Just numbers
        assert!(validate_command_arg("12345").is_ok());
    }
}
