// vapora-shared: Validation pipeline - Runtime validation against Nickel
// contracts Validates JSON inputs against compiled schemas with detailed error
// reporting

use std::sync::Arc;

use regex::Regex;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use tracing::{debug, warn};

use super::schema_registry::{CompiledSchema, Contract, FieldType, SchemaRegistry};
use crate::error::Result;

/// Validation error with field context
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ValidationError {
    /// Field name that failed validation
    pub field: String,

    /// Human-readable error message
    pub message: String,

    /// Contract that was violated
    pub contract: String,

    /// Expected value/type
    pub expected: Option<String>,

    /// Actual value received
    pub actual: Option<String>,
}

impl ValidationError {
    /// Create a new validation error
    pub fn new(
        field: impl Into<String>,
        message: impl Into<String>,
        contract: impl Into<String>,
    ) -> Self {
        Self {
            field: field.into(),
            message: message.into(),
            contract: contract.into(),
            expected: None,
            actual: None,
        }
    }

    /// Add expected value to error
    pub fn with_expected(mut self, expected: impl Into<String>) -> Self {
        self.expected = Some(expected.into());
        self
    }

    /// Add actual value to error
    pub fn with_actual(mut self, actual: impl Into<String>) -> Self {
        self.actual = Some(actual.into());
        self
    }
}

impl std::fmt::Display for ValidationError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Field '{}': {}", self.field, self.message)?;

        if let Some(expected) = &self.expected {
            write!(f, " (expected: {})", expected)?;
        }

        if let Some(actual) = &self.actual {
            write!(f, " (got: {})", actual)?;
        }

        write!(f, " [contract: {}]", self.contract)
    }
}

/// Result of validation pipeline
#[derive(Debug, Serialize, Deserialize)]
pub struct ValidationResult {
    /// Is input valid?
    pub valid: bool,

    /// Validation errors (if any)
    pub errors: Vec<ValidationError>,

    /// Validated data with defaults applied
    pub validated_data: Option<Value>,

    /// Schema name that was used
    pub schema_name: String,
}

impl ValidationResult {
    /// Create successful validation result
    pub fn ok(data: Value, schema_name: impl Into<String>) -> Self {
        Self {
            valid: true,
            errors: vec![],
            validated_data: Some(data),
            schema_name: schema_name.into(),
        }
    }

    /// Create failed validation result
    pub fn err(errors: Vec<ValidationError>, schema_name: impl Into<String>) -> Self {
        Self {
            valid: false,
            errors,
            validated_data: None,
            schema_name: schema_name.into(),
        }
    }

    /// Get all error messages
    pub fn error_messages(&self) -> Vec<String> {
        self.errors.iter().map(|e| e.to_string()).collect()
    }

    /// Check if specific field has error
    pub fn has_field_error(&self, field_name: &str) -> bool {
        self.errors.iter().any(|e| e.field == field_name)
    }
}

/// Main validation pipeline
pub struct ValidationPipeline {
    /// Schema registry
    registry: Arc<SchemaRegistry>,

    /// Enable strict mode (fail on unknown fields)
    strict_mode: bool,
}

impl ValidationPipeline {
    /// Create new validation pipeline
    pub fn new(registry: Arc<SchemaRegistry>) -> Self {
        Self {
            registry,
            strict_mode: false,
        }
    }

    /// Enable strict mode (reject unknown fields)
    pub fn with_strict_mode(mut self, enabled: bool) -> Self {
        self.strict_mode = enabled;
        self
    }

    /// Validate input against a named schema
    pub async fn validate(&self, schema_name: &str, input: &Value) -> Result<ValidationResult> {
        let schema = self.registry.load_schema(schema_name).await?;
        self.validate_against_schema(&schema, input)
    }

    /// Validate input against compiled schema
    pub fn validate_against_schema(
        &self,
        schema: &CompiledSchema,
        input: &Value,
    ) -> Result<ValidationResult> {
        let mut errors = vec![];
        let mut validated = serde_json::Map::new();

        let input_obj = match input.as_object() {
            Some(obj) => obj,
            None => {
                errors.push(ValidationError::new(
                    "<root>",
                    "Input must be a JSON object",
                    "type",
                ));
                return Ok(ValidationResult::err(errors, &schema.name));
            }
        };

        // Validate all defined fields
        for field_schema in &schema.fields {
            let field_value = input_obj.get(&field_schema.name);

            // Check required fields
            if field_schema.required && field_value.is_none() {
                errors.push(
                    ValidationError::new(&field_schema.name, "Required field missing", "required")
                        .with_expected(format!("{:?}", field_schema.field_type)),
                );
                continue;
            }

            // Apply default if missing
            let value = match field_value {
                Some(v) => v.clone(),
                None => match &field_schema.default {
                    Some(default) => {
                        debug!(
                            "Applying default for field {}: {:?}",
                            field_schema.name, default
                        );
                        default.clone()
                    }
                    None => continue,
                },
            };

            // Type validation
            if let Err(e) = self.validate_type(&field_schema.name, &value, &field_schema.field_type)
            {
                errors.push(e);
                continue;
            }

            // Contract validation
            for contract in &field_schema.contracts {
                if let Err(e) = self.validate_contract(&field_schema.name, &value, contract) {
                    errors.push(e);
                }
            }

            validated.insert(field_schema.name.clone(), value);
        }

        // Strict mode: check for unknown fields
        if self.strict_mode {
            for (key, _value) in input_obj {
                if !schema.fields.iter().any(|f| &f.name == key) {
                    errors.push(ValidationError::new(
                        key,
                        "Unknown field (strict mode enabled)",
                        "strict",
                    ));
                }
            }
        }

        if errors.is_empty() {
            Ok(ValidationResult::ok(Value::Object(validated), &schema.name))
        } else {
            Ok(ValidationResult::err(errors, &schema.name))
        }
    }

    /// Validate value type
    fn validate_type(
        &self,
        field_name: &str,
        value: &Value,
        expected: &FieldType,
    ) -> std::result::Result<(), ValidationError> {
        let valid = match expected {
            FieldType::String => value.is_string(),
            FieldType::Number => value.is_number(),
            FieldType::Bool => value.is_boolean(),
            FieldType::Array(_) => value.is_array(),
            FieldType::Object => value.is_object(),
            FieldType::Enum(variants) => value
                .as_str()
                .map(|s| variants.contains(&s.to_string()))
                .unwrap_or(false),
            FieldType::Union(types) => {
                // Valid if matches any type in union
                types
                    .iter()
                    .any(|t| self.validate_type(field_name, value, t).is_ok())
            }
        };

        if valid {
            Ok(())
        } else {
            Err(ValidationError::new(field_name, "Type mismatch", "type")
                .with_expected(format!("{:?}", expected))
                .with_actual(format!("{:?}", value)))
        }
    }

    /// Validate value against contract
    fn validate_contract(
        &self,
        field_name: &str,
        value: &Value,
        contract: &Contract,
    ) -> std::result::Result<(), ValidationError> {
        match contract {
            Contract::NonEmpty => {
                if let Some(s) = value.as_str() {
                    if s.is_empty() {
                        return Err(ValidationError::new(
                            field_name,
                            "String cannot be empty",
                            "std.string.NonEmpty",
                        ));
                    }
                }
            }

            Contract::MinLength(min) => {
                if let Some(s) = value.as_str() {
                    if s.len() < *min {
                        return Err(ValidationError::new(
                            field_name,
                            format!("String too short (minimum {} characters)", min),
                            format!("std.string.length.min {}", min),
                        )
                        .with_expected(format!("≥ {} chars", min))
                        .with_actual(format!("{} chars", s.len())));
                    }
                }
            }

            Contract::MaxLength(max) => {
                if let Some(s) = value.as_str() {
                    if s.len() > *max {
                        return Err(ValidationError::new(
                            field_name,
                            format!("String too long (maximum {} characters)", max),
                            format!("std.string.length.max {}", max),
                        )
                        .with_expected(format!("≤ {} chars", max))
                        .with_actual(format!("{} chars", s.len())));
                    }
                }
            }

            Contract::Pattern(pattern) => {
                if let Some(s) = value.as_str() {
                    let re = Regex::new(pattern).map_err(|e| {
                        ValidationError::new(
                            field_name,
                            format!("Invalid regex pattern: {}", e),
                            "std.string.match",
                        )
                    })?;

                    if !re.is_match(s) {
                        return Err(ValidationError::new(
                            field_name,
                            "Value does not match required pattern",
                            format!("std.string.match {}", pattern),
                        )
                        .with_expected(format!("pattern: {}", pattern))
                        .with_actual(s));
                    }
                }
            }

            Contract::Range { min, max } => {
                if let Some(n) = value.as_f64() {
                    if n < *min || n > *max {
                        return Err(ValidationError::new(
                            field_name,
                            format!("Number must be between {} and {}", min, max),
                            format!("std.number.between {} {}", min, max),
                        )
                        .with_expected(format!("[{}, {}]", min, max))
                        .with_actual(n.to_string()));
                    }
                }
            }

            Contract::GreaterThan(min) => {
                if let Some(n) = value.as_f64() {
                    if n <= *min {
                        return Err(ValidationError::new(
                            field_name,
                            format!("Number must be greater than {}", min),
                            format!("std.number.greater_than {}", min),
                        )
                        .with_expected(format!("> {}", min))
                        .with_actual(n.to_string()));
                    }
                }
            }

            Contract::LessThan(max) => {
                if let Some(n) = value.as_f64() {
                    if n >= *max {
                        return Err(ValidationError::new(
                            field_name,
                            format!("Number must be less than {}", max),
                            format!("std.number.less_than {}", max),
                        )
                        .with_expected(format!("< {}", max))
                        .with_actual(n.to_string()));
                    }
                }
            }

            Contract::Email => {
                if let Some(s) = value.as_str() {
                    // Simple email validation (not RFC-compliant, but good enough)
                    if !s.contains('@') || !s.contains('.') || s.len() < 5 {
                        return Err(ValidationError::new(
                            field_name,
                            "Invalid email format",
                            "std.string.Email",
                        )
                        .with_expected("user@example.com")
                        .with_actual(s));
                    }

                    // Check for multiple @
                    if s.matches('@').count() != 1 {
                        return Err(ValidationError::new(
                            field_name,
                            "Invalid email format (multiple @ symbols)",
                            "std.string.Email",
                        ));
                    }
                }
            }

            Contract::Url => {
                if let Some(s) = value.as_str() {
                    if !s.starts_with("http://") && !s.starts_with("https://") {
                        return Err(ValidationError::new(
                            field_name,
                            "Invalid URL format (must start with http:// or https://)",
                            "std.string.Url",
                        )
                        .with_expected("https://example.com")
                        .with_actual(s));
                    }
                }
            }

            Contract::Uuid => {
                if let Some(s) = value.as_str() {
                    let uuid_pattern =
                        r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$";
                    let re = Regex::new(uuid_pattern).unwrap();

                    if !re.is_match(s) {
                        return Err(ValidationError::new(
                            field_name,
                            "Invalid UUID format",
                            "std.string.Uuid",
                        )
                        .with_expected("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
                        .with_actual(s));
                    }
                }
            }

            Contract::Custom(source) => {
                // Custom contracts require Nickel evaluation
                // For now, warn and skip
                warn!(
                    "Custom contract validation not yet implemented for field {}: {}",
                    field_name, source
                );
            }
        }

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use serde_json::json;

    use super::*;
    use crate::validation::schema_registry::{CompiledSchema, Contract, FieldSchema, FieldType};

    fn create_test_schema() -> CompiledSchema {
        CompiledSchema {
            name: "test_schema".to_string(),
            fields: vec![
                FieldSchema {
                    name: "title".to_string(),
                    field_type: FieldType::String,
                    required: true,
                    contracts: vec![Contract::NonEmpty, Contract::MaxLength(100)],
                    default: None,
                    doc: Some("Task title".to_string()),
                },
                FieldSchema {
                    name: "priority".to_string(),
                    field_type: FieldType::Number,
                    required: true,
                    contracts: vec![Contract::Range {
                        min: 0.0,
                        max: 100.0,
                    }],
                    default: None,
                    doc: Some("Priority score".to_string()),
                },
                FieldSchema {
                    name: "email".to_string(),
                    field_type: FieldType::String,
                    required: false,
                    contracts: vec![Contract::Email],
                    default: Some(json!("noreply@example.com")),
                    doc: None,
                },
            ],
            custom_contracts: vec![],
            source_path: PathBuf::from("test.ncl"),
        }
    }

    #[test]
    fn test_validation_error_display() {
        let error = ValidationError::new("title", "Field is required", "required")
            .with_expected("String")
            .with_actual("null");

        let display = error.to_string();
        assert!(display.contains("title"));
        assert!(display.contains("required"));
        assert!(display.contains("String"));
    }

    #[test]
    fn test_validation_result_ok() {
        let data = json!({"title": "Test"});
        let result = ValidationResult::ok(data.clone(), "test_schema");

        assert!(result.valid);
        assert!(result.errors.is_empty());
        assert_eq!(result.validated_data, Some(data));
    }

    #[test]
    fn test_validation_result_err() {
        let errors = vec![ValidationError::new("field1", "Error", "contract1")];
        let result = ValidationResult::err(errors, "test_schema");

        assert!(!result.valid);
        assert_eq!(result.errors.len(), 1);
        assert!(result.validated_data.is_none());
    }

    #[tokio::test]
    async fn test_validate_valid_input() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        let input = json!({
            "title": "Test Task",
            "priority": 50.0
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(result.valid, "Validation should pass for valid input");
        assert!(result.errors.is_empty());
    }

    #[tokio::test]
    async fn test_validate_missing_required_field() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        let input = json!({
            "priority": 50.0
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(!result.valid);
        assert!(result.has_field_error("title"));
    }

    #[tokio::test]
    async fn test_validate_contract_violation() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        let input = json!({
            "title": "", // Empty string violates NonEmpty
            "priority": 50.0
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(!result.valid);
        assert!(result.has_field_error("title"));
    }

    #[tokio::test]
    async fn test_validate_with_defaults() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        let input = json!({
            "title": "Test",
            "priority": 50.0
            // email not provided, should use default
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(result.valid);

        let validated = result.validated_data.unwrap();
        assert_eq!(validated["email"], "noreply@example.com");
    }

    #[tokio::test]
    async fn test_validate_number_range() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        // Out of range
        let input = json!({
            "title": "Test",
            "priority": 150.0 // > 100
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(!result.valid);
        assert!(result.has_field_error("priority"));
    }

    #[tokio::test]
    async fn test_validate_email_format() {
        let registry = Arc::new(SchemaRegistry::new(PathBuf::from("/tmp")));
        let pipeline = ValidationPipeline::new(registry);
        let schema = create_test_schema();

        let input = json!({
            "title": "Test",
            "priority": 50.0,
            "email": "invalid-email" // Missing @
        });

        let result = pipeline.validate_against_schema(&schema, &input).unwrap();
        assert!(!result.valid);
        assert!(result.has_field_error("email"));
    }
}
