//! Configuration validation with type checking, range validation, and regex matching

use crate::error::{GatewayConfigError, GatewayConfigResult};
use regex::Regex;
use serde_json::Value;
use std::collections::HashMap;

/// Configuration validator supporting type, range, regex, and enum validation
pub struct ConfigValidator {
    /// Compiled regex patterns cache
    regex_cache: HashMap<String, Regex>,
}

/// Validation rules from database
#[derive(Debug, Clone, serde::Deserialize)]
pub struct ValidationRules {
    /// Minimum value for numbers
    pub min: Option<f64>,
    /// Maximum value for numbers
    pub max: Option<f64>,
    /// Minimum length for strings/arrays
    pub min_len: Option<usize>,
    /// Maximum length for strings/arrays
    pub max_len: Option<usize>,
    /// Regex pattern for strings
    pub regex: Option<String>,
    /// Allowed enum values
    #[serde(rename = "enum")]
    pub enum_values: Option<Vec<String>>,
}

impl ConfigValidator {
    /// Creates a new ConfigValidator
    pub fn new() -> Self {
        Self {
            regex_cache: HashMap::new(),
        }
    }

    /// Validates a configuration value against its data type and validation rules
    ///
    /// # Arguments
    /// * `value` - The configuration value to validate
    ///
    /// * `data_type` - Expected data type (string, integer, float, boolean, json, array)
    /// * `rules` - Optional validation rules
    ///
    /// # Returns
    ///
    /// Ok(()) if validation passes, Err with descriptive message otherwise
    pub fn validate(
        &mut self,
        value: &Value,
        data_type: &str,
        rules: Option<&Value>,
    ) -> GatewayConfigResult<()> {
        // Parse validation rules if provided
        let validation_rules: Option<ValidationRules> = match rules {
            Some(r) => serde_json::from_value(r.clone()).ok(),
            None => None,
        };

        // Type validation
        self.validate_type(value, data_type)?;

        // Additional validations based on rules
        if let Some(rules) = validation_rules {
            match data_type {
                "integer" | "float" => self.validate_numeric(value, &rules)?,
                "string" => self.validate_string(value, &rules)?,
                "array" => self.validate_array(value, &rules)?,
                _ => {},
            }
        }

        Ok(())
    }

    /// Validates that the value matches the expected type
    fn validate_type(&self, value: &Value, data_type: &str) -> GatewayConfigResult<()> {
        let matches = match data_type {
            "string" => value.is_string(),
            "integer" => value.is_i64() || value.is_u64(),
            "float" => value.is_f64() || value.is_i64() || value.is_u64(),
            "boolean" => value.is_boolean(),
            "json" => value.is_object(),
            "array" => value.is_array(),
            _ => {
                return Err(GatewayConfigError::Validation(format!(
                    "Unknown data type: {}",
                    data_type
                )))
            },
        };

        if !matches {
            return Err(GatewayConfigError::Validation(format!(
                "Type mismatch: expected {}, got {}",
                data_type,
                value_type_name(value)
            )));
        }

        Ok(())
    }

    /// Validates numeric values against min/max rules
    fn validate_numeric(&self, value: &Value, rules: &ValidationRules) -> GatewayConfigResult<()> {
        let num = value
            .as_f64()
            .ok_or_else(|| GatewayConfigError::Validation("Not a number".to_string()))?;

        if let Some(min) = rules.min {
            if num < min {
                return Err(GatewayConfigError::Validation(format!(
                    "Value {} is below minimum {}",
                    num, min
                )));
            }
        }

        if let Some(max) = rules.max {
            if num > max {
                return Err(GatewayConfigError::Validation(format!(
                    "Value {} exceeds maximum {}",
                    num, max
                )));
            }
        }

        Ok(())
    }

    /// Validates string values against length and regex rules
    fn validate_string(&mut self, value: &Value, rules: &ValidationRules) -> GatewayConfigResult<()> {
        let s = value
            .as_str()
            .ok_or_else(|| GatewayConfigError::Validation("Not a string".to_string()))?;

        // Length validation
        if let Some(min_len) = rules.min_len {
            if s.len() < min_len {
                return Err(GatewayConfigError::Validation(format!(
                    "String length {} is below minimum {}",
                    s.len(),
                    min_len
                )));
            }
        }

        if let Some(max_len) = rules.max_len {
            if s.len() > max_len {
                return Err(GatewayConfigError::Validation(format!(
                    "String length {} exceeds maximum {}",
                    s.len(),
                    max_len
                )));
            }
        }

        // Regex validation
        if let Some(pattern) = &rules.regex {
            if !self.regex_cache.contains_key(pattern) {
                let compiled = Regex::new(pattern).map_err(|e| {
                    GatewayConfigError::Validation(format!("Invalid regex pattern '{}': {}", pattern, e))
                })?;
                self.regex_cache.insert(pattern.clone(), compiled);
            }
            let regex = self
                .regex_cache
                .get(pattern)
                .ok_or_else(|| GatewayConfigError::Validation("Regex cache miss".to_owned()))?;

            if !regex.is_match(s) {
                return Err(GatewayConfigError::Validation(format!(
                    "String '{}' does not match pattern '{}'",
                    s, pattern
                )));
            }
        }

        // Enum validation
        if let Some(enum_values) = &rules.enum_values {
            if !enum_values.contains(&s.to_string()) {
                return Err(GatewayConfigError::Validation(format!(
                    "Value '{}' is not in allowed values: {:?}",
                    s, enum_values
                )));
            }
        }

        Ok(())
    }

    /// Validates array values against length rules
    fn validate_array(&self, value: &Value, rules: &ValidationRules) -> GatewayConfigResult<()> {
        let arr = value
            .as_array()
            .ok_or_else(|| GatewayConfigError::Validation("Not an array".to_string()))?;

        if let Some(min_len) = rules.min_len {
            if arr.len() < min_len {
                return Err(GatewayConfigError::Validation(format!(
                    "Array length {} is below minimum {}",
                    arr.len(),
                    min_len
                )));
            }
        }

        if let Some(max_len) = rules.max_len {
            if arr.len() > max_len {
                return Err(GatewayConfigError::Validation(format!(
                    "Array length {} exceeds maximum {}",
                    arr.len(),
                    max_len
                )));
            }
        }

        Ok(())
    }
}

impl Default for ConfigValidator {
    fn default() -> Self {
        Self::new()
    }
}

/// Returns a human-readable name for a `JSON` value's type
fn value_type_name(value: &Value) -> &'static str {
    match value {
        Value::Null => "null",
        Value::Bool(_) => "boolean",
        Value::Number(_) => "number",
        Value::String(_) => "string",
        Value::Array(_) => "array",
        Value::Object(_) => "object",
    }
}

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

    #[test]
    fn test_validate_integer_type() {
        let mut validator = ConfigValidator::new();
        assert!(validator.validate(&json!(42), "integer", None).is_ok());
        assert!(validator
            .validate(&json!("not a number"), "integer", None)
            .is_err());
    }

    #[test]
    fn test_validate_float_type() {
        let mut validator = ConfigValidator::new();
        assert!(validator.validate(&json!(std::f64::consts::PI), "float", None).is_ok());
        assert!(validator.validate(&json!(42), "float", None).is_ok()); // Integers valid as floats
    }

    #[test]
    fn test_validate_string_type() {
        let mut validator = ConfigValidator::new();
        assert!(validator.validate(&json!("hello"), "string", None).is_ok());
        assert!(validator.validate(&json!(123), "string", None).is_err());
    }

    #[test]
    fn test_validate_numeric_range() {
        let mut validator = ConfigValidator::new();
        let rules = json!({"min": 0.0, "max": 100.0});

        assert!(validator
            .validate(&json!(50.0), "float", Some(&rules))
            .is_ok());
        assert!(validator
            .validate(&json!(-1.0), "float", Some(&rules))
            .is_err());
        assert!(validator
            .validate(&json!(101.0), "float", Some(&rules))
            .is_err());
    }

    #[test]
    fn test_validate_string_length() {
        let mut validator = ConfigValidator::new();
        let rules = json!({"min_len": 3, "max_len": 10});

        assert!(validator
            .validate(&json!("hello"), "string", Some(&rules))
            .is_ok());
        assert!(validator
            .validate(&json!("hi"), "string", Some(&rules))
            .is_err());
        assert!(validator
            .validate(&json!("this is too long"), "string", Some(&rules))
            .is_err());
    }

    #[test]
    fn test_validate_regex() {
        let mut validator = ConfigValidator::new();
        let rules = json!({"regex": "^[A-Z]{3}$"});

        assert!(validator
            .validate(&json!("USD"), "string", Some(&rules))
            .is_ok());
        assert!(validator
            .validate(&json!("usd"), "string", Some(&rules))
            .is_err());
        assert!(validator
            .validate(&json!("US"), "string", Some(&rules))
            .is_err());
    }

    #[test]
    fn test_validate_enum() {
        let mut validator = ConfigValidator::new();
        let rules = json!({"enum": ["FIXED", "VOLUME_BASED", "SPREAD_BASED"]});

        assert!(validator
            .validate(&json!("FIXED"), "string", Some(&rules))
            .is_ok());
        assert!(validator
            .validate(&json!("INVALID"), "string", Some(&rules))
            .is_err());
    }

    #[test]
    fn test_validate_array_length() {
        let mut validator = ConfigValidator::new();
        let rules = json!({"min_len": 2, "max_len": 5});

        assert!(validator
            .validate(&json!([1, 2, 3]), "array", Some(&rules))
            .is_ok());
        assert!(validator
            .validate(&json!([1]), "array", Some(&rules))
            .is_err());
        assert!(validator
            .validate(&json!([1, 2, 3, 4, 5, 6]), "array", Some(&rules))
            .is_err());
    }
}
