//! Database schema utilities
//!
//! This module provides utilities for working with database schemas,
//! including query builders and schema validation.

use serde_json::Value as JsonValue;

use crate::error::{Error, Result};

/// Database schema name
pub const SCHEMA_NAME: &str = "attune";

/// Table identifiers
#[derive(Debug, Clone, Copy)]
pub enum Table {
    Pack,
    Runtime,
    Worker,
    Trigger,
    Sensor,
    Action,
    Rule,
    Event,
    Enforcement,
    Execution,
    Inquiry,
    Identity,
    PermissionSet,
    PermissionAssignment,
    Policy,
    Key,
    Notification,
    Artifact,
}

impl Table {
    /// Get the table name as a string
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Pack => "pack",
            Self::Runtime => "runtime",
            Self::Worker => "worker",
            Self::Trigger => "trigger",
            Self::Sensor => "sensor",
            Self::Action => "action",
            Self::Rule => "rule",
            Self::Event => "event",
            Self::Enforcement => "enforcement",
            Self::Execution => "execution",
            Self::Inquiry => "inquiry",
            Self::Identity => "identity",
            Self::PermissionSet => "permission_set",
            Self::PermissionAssignment => "permission_assignment",
            Self::Policy => "policy",
            Self::Key => "key",
            Self::Notification => "notification",
            Self::Artifact => "artifact",
        }
    }
}

/// Common column identifiers
#[derive(Debug, Clone, Copy)]
pub enum Column {
    Id,
    Ref,
    Pack,
    PackRef,
    Label,
    Description,
    Version,
    Name,
    Status,
    Created,
    Updated,
    Enabled,
    Config,
    Meta,
    Tags,
    RuntimeType,
    WorkerType,
    Entrypoint,
    Runtime,
    RuntimeRef,
    Trigger,
    TriggerRef,
    Action,
    ActionRef,
    Rule,
    RuleRef,
    ParamSchema,
    OutSchema,
    ConfSchema,
    Payload,
    Response,
    ResponseSchema,
    Result,
    Execution,
    Enforcement,
    Executor,
    Prompt,
    AssignedTo,
    TimeoutAt,
    RespondedAt,
    Login,
    DisplayName,
    Attributes,
    Owner,
    OwnerType,
    Encrypted,
    Value,
    Channel,
    Entity,
    EntityType,
    Activity,
    State,
    Content,
}

/// JSON Schema validator
pub struct SchemaValidator {
    schema: JsonValue,
}

impl SchemaValidator {
    /// Create a new schema validator
    pub fn new(schema: JsonValue) -> Result<Self> {
        // Validate that the schema itself is valid JSON Schema
        if !schema.is_object() {
            return Err(Error::schema_validation("Schema must be a JSON object"));
        }

        Ok(Self { schema })
    }

    /// Validate data against the schema
    pub fn validate(&self, data: &JsonValue) -> Result<()> {
        // Use jsonschema crate for validation
        let compiled = jsonschema::validator_for(&self.schema)
            .map_err(|e| Error::schema_validation(format!("Failed to compile schema: {}", e)))?;

        if let Err(error) = compiled.validate(data) {
            return Err(Error::schema_validation(format!(
                "Validation failed: {}",
                error
            )));
        }
        Ok(())
    }

    /// Get the underlying schema
    pub fn schema(&self) -> &JsonValue {
        &self.schema
    }
}

/// Reference format validator
pub struct RefValidator;

impl RefValidator {
    /// Validate pack.component format (e.g., "core.webhook")
    pub fn validate_component_ref(ref_str: &str) -> Result<()> {
        let parts: Vec<&str> = ref_str.split('.').collect();
        if parts.len() != 2 {
            return Err(Error::validation(format!(
                "Invalid component reference format: '{}'. Expected 'pack.component'",
                ref_str
            )));
        }

        Self::validate_identifier(parts[0])?;
        Self::validate_identifier(parts[1])?;

        Ok(())
    }

    /// Validate pack.name format (e.g., "core.python", "core.shell")
    pub fn validate_runtime_ref(ref_str: &str) -> Result<()> {
        let parts: Vec<&str> = ref_str.split('.').collect();
        if parts.len() != 2 {
            return Err(Error::validation(format!(
                "Invalid runtime reference format: '{}'. Expected 'pack.name' (e.g., 'core.python')",
                ref_str
            )));
        }

        Self::validate_identifier(parts[0])?;
        Self::validate_identifier(parts[1])?;

        Ok(())
    }

    /// Validate pack reference format (simple identifier)
    pub fn validate_pack_ref(ref_str: &str) -> Result<()> {
        Self::validate_identifier(ref_str)
    }

    /// Validate identifier (lowercase alphanumeric with hyphens/underscores)
    fn validate_identifier(identifier: &str) -> Result<()> {
        if identifier.is_empty() {
            return Err(Error::validation("Identifier cannot be empty"));
        }

        // Must start with lowercase letter
        if !identifier.chars().next().unwrap().is_ascii_lowercase() {
            return Err(Error::validation(format!(
                "Identifier '{}' must start with a lowercase letter",
                identifier
            )));
        }

        // Must contain only lowercase alphanumeric, hyphens, or underscores
        for ch in identifier.chars() {
            if !ch.is_ascii_lowercase() && !ch.is_ascii_digit() && ch != '-' && ch != '_' {
                return Err(Error::validation(format!(
                    "Identifier '{}' contains invalid character '{}'. Only lowercase letters, digits, hyphens, and underscores are allowed",
                    identifier, ch
                )));
            }
        }

        Ok(())
    }
}

/// Build a qualified table name with schema
pub fn qualified_table(table: Table) -> String {
    format!("{}.{}", SCHEMA_NAME, table.as_str())
}

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

    #[test]
    fn test_table_as_str() {
        assert_eq!(Table::Pack.as_str(), "pack");
        assert_eq!(Table::Action.as_str(), "action");
        assert_eq!(Table::Execution.as_str(), "execution");
    }

    #[test]
    fn test_qualified_table() {
        assert_eq!(qualified_table(Table::Pack), "attune.pack");
        assert_eq!(qualified_table(Table::Action), "attune.action");
    }

    #[test]
    fn test_ref_validator_component() {
        assert!(RefValidator::validate_component_ref("core.webhook").is_ok());
        assert!(RefValidator::validate_component_ref("my-pack.my-action").is_ok());
        assert!(RefValidator::validate_component_ref("pack_name.component_name").is_ok());

        // Invalid formats
        assert!(RefValidator::validate_component_ref("nopack").is_err());
        assert!(RefValidator::validate_component_ref("too.many.parts").is_err());
        assert!(RefValidator::validate_component_ref("Capital.name").is_err());
        assert!(RefValidator::validate_component_ref("pack.Name").is_err());
    }

    #[test]
    fn test_ref_validator_runtime() {
        assert!(RefValidator::validate_runtime_ref("core.python").is_ok());
        assert!(RefValidator::validate_runtime_ref("core.shell").is_ok());
        assert!(RefValidator::validate_runtime_ref("mypack.nodejs").is_ok());
        assert!(RefValidator::validate_runtime_ref("core.native").is_ok());

        // Invalid formats
        assert!(RefValidator::validate_runtime_ref("core.action.webhook").is_err()); // 3-part no longer valid
        assert!(RefValidator::validate_runtime_ref("python").is_err()); // missing pack
        assert!(RefValidator::validate_runtime_ref("Core.python").is_err()); // uppercase
    }

    #[test]
    fn test_ref_validator_pack() {
        assert!(RefValidator::validate_pack_ref("core").is_ok());
        assert!(RefValidator::validate_pack_ref("my-pack").is_ok());
        assert!(RefValidator::validate_pack_ref("pack_name").is_ok());

        // Invalid formats
        assert!(RefValidator::validate_pack_ref("").is_err());
        assert!(RefValidator::validate_pack_ref("Core").is_err());
        assert!(RefValidator::validate_pack_ref("pack.name").is_err()); // dots are not allowed in pack refs
        assert!(RefValidator::validate_pack_ref("pack name").is_err());
    }

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

        let validator = SchemaValidator::new(schema).unwrap();

        // Valid data
        let valid_data = json!({"name": "John", "age": 30});
        assert!(validator.validate(&valid_data).is_ok());

        // Missing required field
        let invalid_data = json!({"age": 30});
        assert!(validator.validate(&invalid_data).is_err());

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

    #[test]
    fn test_schema_validator_invalid_schema() {
        let invalid_schema = json!("not an object");
        assert!(SchemaValidator::new(invalid_schema).is_err());
    }
}
