//! Schema definitions and validation for documents.

use crate::error::DatabaseError;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Field types supported in schemas.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum FieldType {
    /// String field.
    String,
    /// Integer (i64).
    Integer,
    /// Floating point (f64).
    Float,
    /// Boolean.
    Boolean,
    /// Array of values.
    Array(Box<FieldType>),
    /// Nested object.
    Object,
    /// Binary data.
    Binary,
    /// Timestamp (milliseconds since epoch).
    Timestamp,
    /// Vector embedding (f32 array).
    Vector(u32), // dimension
    /// Any type (no validation).
    Any,
}

/// Field definition in a schema.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Field {
    /// Field name.
    pub name: String,
    /// Field type.
    pub field_type: FieldType,
    /// Whether field is required.
    pub required: bool,
    /// Whether field should be indexed.
    pub indexed: bool,
    /// Default value (JSON).
    pub default: Option<serde_json::Value>,
    /// Field description.
    pub description: Option<String>,
}

impl Field {
    /// Creates a new required field.
    pub fn required(name: impl Into<String>, field_type: FieldType) -> Self {
        Self {
            name: name.into(),
            field_type,
            required: true,
            indexed: false,
            default: None,
            description: None,
        }
    }

    /// Creates a new optional field.
    pub fn optional(name: impl Into<String>, field_type: FieldType) -> Self {
        Self {
            name: name.into(),
            field_type,
            required: false,
            indexed: false,
            default: None,
            description: None,
        }
    }

    /// Sets the field as indexed.
    pub fn with_index(mut self) -> Self {
        self.indexed = true;
        self
    }

    /// Sets a default value.
    pub fn with_default(mut self, default: serde_json::Value) -> Self {
        self.default = Some(default);
        self
    }

    /// Sets description.
    pub fn with_description(mut self, desc: impl Into<String>) -> Self {
        self.description = Some(desc.into());
        self
    }
}

/// Document schema for validation.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Schema {
    /// Schema name.
    pub name: String,
    /// Fields in the schema.
    pub fields: Vec<Field>,
    /// Whether to allow additional fields.
    pub strict: bool,
    /// Schema version.
    pub version: u32,
}

impl Schema {
    /// Creates a new schema.
    pub fn new(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            fields: Vec::new(),
            strict: false,
            version: 1,
        }
    }

    /// Adds a field to the schema.
    pub fn field(mut self, field: Field) -> Self {
        self.fields.push(field);
        self
    }

    /// Sets strict mode (no additional fields allowed).
    pub fn strict(mut self) -> Self {
        self.strict = true;
        self
    }

    /// Returns fields that should be indexed.
    pub fn indexed_fields(&self) -> Vec<&Field> {
        self.fields.iter().filter(|f| f.indexed).collect()
    }

    /// Returns required fields.
    pub fn required_fields(&self) -> Vec<&Field> {
        self.fields.iter().filter(|f| f.required).collect()
    }
}

/// Schema validator.
pub struct SchemaValidator {
    schemas: HashMap<String, Schema>,
}

impl SchemaValidator {
    /// Creates a new validator.
    pub fn new() -> Self {
        Self {
            schemas: HashMap::new(),
        }
    }

    /// Registers a schema.
    pub fn register(&mut self, schema: Schema) {
        self.schemas.insert(schema.name.clone(), schema);
    }

    /// Gets a schema by name.
    pub fn get(&self, name: &str) -> Option<&Schema> {
        self.schemas.get(name)
    }

    /// Validates a document against a schema.
    pub fn validate(
        &self,
        schema_name: &str,
        document: &serde_json::Value,
    ) -> Result<(), DatabaseError> {
        let schema = self
            .schemas
            .get(schema_name)
            .ok_or_else(|| DatabaseError::NotFound(format!("Schema: {}", schema_name)))?;

        let obj = document.as_object().ok_or_else(|| {
            DatabaseError::SchemaValidation("Document must be an object".to_string())
        })?;

        // Check required fields
        for field in &schema.fields {
            if field.required && !obj.contains_key(&field.name) {
                return Err(DatabaseError::SchemaValidation(format!(
                    "Missing required field: {}",
                    field.name
                )));
            }
        }

        // Check field types
        for field in &schema.fields {
            if let Some(value) = obj.get(&field.name) {
                self.validate_field_type(value, &field.field_type, &field.name)?;
            }
        }

        // Check for extra fields in strict mode
        if schema.strict {
            let known_fields: Vec<&str> = schema.fields.iter().map(|f| f.name.as_str()).collect();
            for key in obj.keys() {
                if !known_fields.contains(&key.as_str()) {
                    return Err(DatabaseError::SchemaValidation(format!(
                        "Unknown field in strict schema: {}",
                        key
                    )));
                }
            }
        }

        Ok(())
    }

    fn validate_field_type(
        &self,
        value: &serde_json::Value,
        expected: &FieldType,
        field_name: &str,
    ) -> Result<(), DatabaseError> {
        let valid = match expected {
            FieldType::String => value.is_string(),
            FieldType::Integer => value.is_i64(),
            FieldType::Float => value.is_f64() || value.is_i64(),
            FieldType::Boolean => value.is_boolean(),
            FieldType::Array(inner) => {
                if let Some(arr) = value.as_array() {
                    for item in arr {
                        self.validate_field_type(item, inner, field_name)?;
                    }
                    true
                } else {
                    false
                }
            }
            FieldType::Object => value.is_object(),
            FieldType::Binary => value.is_string(), // Base64 encoded
            FieldType::Timestamp => value.is_i64() || value.is_u64(),
            FieldType::Vector(dim) => {
                if let Some(arr) = value.as_array() {
                    arr.len() == *dim as usize && arr.iter().all(|v| v.is_f64())
                } else {
                    false
                }
            }
            FieldType::Any => true,
        };

        if valid {
            Ok(())
        } else {
            Err(DatabaseError::SchemaValidation(format!(
                "Field '{}' has invalid type, expected {:?}",
                field_name, expected
            )))
        }
    }
}

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

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

    #[test]
    fn test_schema_validation() {
        let schema = Schema::new("user")
            .field(Field::required("name", FieldType::String))
            .field(Field::required("age", FieldType::Integer))
            .field(Field::optional("email", FieldType::String));

        let mut validator = SchemaValidator::new();
        validator.register(schema);

        // Valid document
        let doc = json!({
            "name": "Alice",
            "age": 30,
            "email": "alice@example.com"
        });
        assert!(validator.validate("user", &doc).is_ok());

        // Missing required field
        let doc = json!({
            "name": "Bob"
        });
        assert!(validator.validate("user", &doc).is_err());

        // Wrong type
        let doc = json!({
            "name": "Charlie",
            "age": "thirty"
        });
        assert!(validator.validate("user", &doc).is_err());
    }

    #[test]
    fn test_strict_schema() {
        let schema = Schema::new("strict_user")
            .field(Field::required("name", FieldType::String))
            .strict();

        let mut validator = SchemaValidator::new();
        validator.register(schema);

        // Extra field should fail
        let doc = json!({
            "name": "Alice",
            "extra": "not allowed"
        });
        assert!(validator.validate("strict_user", &doc).is_err());
    }

    #[test]
    fn test_vector_field() {
        let schema = Schema::new("embedding")
            .field(Field::required("vector", FieldType::Vector(3)));

        let mut validator = SchemaValidator::new();
        validator.register(schema);

        // Valid vector
        let doc = json!({
            "vector": [1.0, 2.0, 3.0]
        });
        assert!(validator.validate("embedding", &doc).is_ok());

        // Wrong dimension
        let doc = json!({
            "vector": [1.0, 2.0]
        });
        assert!(validator.validate("embedding", &doc).is_err());
    }
}
