//! Input validation for MCP tools
//!
//! Enforces safe limits on user inputs to prevent abuse and resource
//! exhaustion.

use anyhow::{anyhow, Result};
use serde_json::Value;

/// Maximum length for string fields (titles, content, etc.)
pub const MAX_STRING_LENGTH: usize = 10_000;

/// Maximum length for short fields (IDs, names, etc.)
pub const MAX_SHORT_LENGTH: usize = 256;

/// Maximum number of items in array fields (tags, relates_to, etc.)
pub const MAX_ARRAY_ITEMS: usize = 100;

/// Maximum limit value for result sets
pub const MAX_LIMIT: usize = 1000;

/// Validates and sanitizes a required string field
pub fn validate_required_string(
    value: Option<&str>,
    field_name: &str,
    max_length: usize,
) -> Result<String> {
    let s = value.ok_or_else(|| anyhow!("Missing required field: {}", field_name))?;

    if s.is_empty() {
        return Err(anyhow!("Field '{}' cannot be empty", field_name));
    }

    if s.len() > max_length {
        return Err(anyhow!(
            "Field '{}' exceeds maximum length of {} characters",
            field_name,
            max_length
        ));
    }

    Ok(s.trim().to_string())
}

/// Validates an optional string field
pub fn validate_optional_string(
    value: Option<&str>,
    field_name: &str,
    max_length: usize,
) -> Result<Option<String>> {
    match value {
        Some(s) => {
            if s.is_empty() {
                return Ok(None);
            }

            if s.len() > max_length {
                return Err(anyhow!(
                    "Field '{}' exceeds maximum length of {} characters",
                    field_name,
                    max_length
                ));
            }

            Ok(Some(s.trim().to_string()))
        }
        None => Ok(None),
    }
}

/// Validates a limit parameter (for pagination)
pub fn validate_limit(value: Option<u64>, default: usize) -> Result<usize> {
    match value {
        Some(limit) => {
            if limit == 0 {
                return Err(anyhow!("Limit must be greater than 0"));
            }
            if limit as usize > MAX_LIMIT {
                return Err(anyhow!("Limit cannot exceed {}", MAX_LIMIT));
            }
            Ok(limit as usize)
        }
        None => Ok(default),
    }
}

/// Validates a numeric strength value (0.0 to 1.0)
pub fn validate_strength(value: Option<f64>) -> Result<f32> {
    let strength = value.unwrap_or(1.0);
    if !(0.0..=1.0).contains(&strength) {
        return Err(anyhow!(
            "Strength must be between 0 and 1, got {}",
            strength
        ));
    }
    Ok(strength as f32)
}

/// Validates an array of string items
pub fn validate_string_array(
    value: Option<&Vec<Value>>,
    field_name: &str,
    max_items: usize,
    max_item_length: usize,
) -> Result<Vec<String>> {
    match value {
        Some(arr) => {
            if arr.len() > max_items {
                return Err(anyhow!(
                    "Field '{}' cannot have more than {} items",
                    field_name,
                    max_items
                ));
            }

            arr.iter()
                .enumerate()
                .map(|(i, v)| {
                    let s = v
                        .as_str()
                        .ok_or_else(|| anyhow!("Item {} in '{}' is not a string", i, field_name))?;

                    if s.is_empty() {
                        return Err(anyhow!("Item {} in '{}' cannot be empty", i, field_name));
                    }

                    if s.len() > max_item_length {
                        return Err(anyhow!(
                            "Item {} in '{}' exceeds maximum length of {} characters",
                            i,
                            field_name,
                            max_item_length
                        ));
                    }

                    Ok(s.trim().to_string())
                })
                .collect()
        }
        None => Ok(Vec::new()),
    }
}

/// Validates a node ID reference (must exist is checked at call site)
pub fn validate_node_id(value: Option<&str>, field_name: &str) -> Result<String> {
    validate_required_string(value, field_name, MAX_SHORT_LENGTH)
}

/// Validates an enum-like field against allowed values
pub fn validate_enum(value: Option<&str>, field_name: &str, allowed: &[&str]) -> Result<String> {
    let s = validate_required_string(value, field_name, MAX_SHORT_LENGTH)?;

    if !allowed.contains(&s.as_str()) {
        return Err(anyhow!(
            "Invalid value for '{}': '{}'. Must be one of: {}",
            field_name,
            s,
            allowed.join(", ")
        ));
    }

    Ok(s)
}

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

    #[test]
    fn test_validate_required_string() {
        let result = validate_required_string(Some("hello"), "test", 100);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), "hello");

        let result = validate_required_string(None, "test", 100);
        assert!(result.is_err());

        let result = validate_required_string(Some(""), "test", 100);
        assert!(result.is_err());

        let long_string = "a".repeat(101);
        let result = validate_required_string(Some(&long_string), "test", 100);
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_limit() {
        let result = validate_limit(Some(10), 5);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 10);

        let result = validate_limit(None, 5);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 5);

        let result = validate_limit(Some(0), 5);
        assert!(result.is_err());

        let result = validate_limit(Some((MAX_LIMIT + 1) as u64), 5);
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_strength() {
        let result = validate_strength(Some(0.5));
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 0.5);

        let result = validate_strength(None);
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 1.0);

        let result = validate_strength(Some(1.5));
        assert!(result.is_err());

        let result = validate_strength(Some(-0.1));
        assert!(result.is_err());
    }

    #[test]
    fn test_validate_enum() {
        let result = validate_enum(Some("note"), "type", &["note", "decision", "guideline"]);
        assert!(result.is_ok());

        let result = validate_enum(Some("invalid"), "type", &["note", "decision"]);
        assert!(result.is_err());
    }
}
