//! Schema loading from various file formats.
//!
//! Supports YAML, JSON, and DDL (SQL) schema files.

use super::types::SchemaDefinition;
use std::path::Path;
use thiserror::Error;

/// Errors that can occur when loading a schema.
#[derive(Debug, Error)]
pub enum SchemaLoadError {
    /// Failed to read the schema file from disk.
    #[error("Failed to read schema file: {0}")]
    IoError(#[from] std::io::Error),

    /// Failed to parse the YAML content.
    #[error("Failed to parse YAML schema: {0}")]
    YamlError(#[from] serde_yaml::Error),

    /// Failed to parse the JSON content.
    #[error("Failed to parse JSON schema: {0}")]
    JsonError(#[from] serde_json::Error),

    /// The file extension is not a supported format.
    #[error("Unsupported schema file format: {0}. Supported: .yaml, .yml, .json, .sql")]
    UnsupportedFormat(String),

    /// The schema content is structurally invalid.
    #[error("Schema validation error: {0}")]
    ValidationError(String),

    /// Failed to parse DDL statements.
    #[error("Failed to parse DDL: {0}")]
    DdlParseError(String),
}

impl SchemaDefinition {
    /// Load a schema from a YAML string.
    pub fn from_yaml(yaml: &str) -> Result<Self, SchemaLoadError> {
        let schema: SchemaDefinition = serde_yaml::from_str(yaml)?;
        schema.validate()?;
        Ok(schema)
    }

    /// Load a schema from a JSON string.
    pub fn from_json(json: &str) -> Result<Self, SchemaLoadError> {
        let schema: SchemaDefinition = serde_json::from_str(json)?;
        schema.validate()?;
        Ok(schema)
    }

    /// Load a schema from a file. Format is auto-detected from extension.
    ///
    /// Supported extensions: `.yaml`, `.yml`, `.json`, `.sql`.
    pub fn from_file(path: &Path) -> Result<Self, SchemaLoadError> {
        let ext = path
            .extension()
            .and_then(|e| e.to_str())
            .unwrap_or("")
            .to_lowercase();

        match ext.as_str() {
            "yaml" | "yml" => {
                let content = std::fs::read_to_string(path)?;
                Self::from_yaml(&content)
            }
            "json" => {
                let content = std::fs::read_to_string(path)?;
                Self::from_json(&content)
            }
            "sql" => {
                use super::ddl::parse_ddl_file;
                use super::types::SqlDialect;
                parse_ddl_file(path, SqlDialect::Generic)
            }
            other => Err(SchemaLoadError::UnsupportedFormat(other.to_string())),
        }
    }

    /// Load a schema from a DDL string using the default (Generic) dialect.
    pub fn from_ddl(ddl: &str) -> Result<Self, SchemaLoadError> {
        use super::ddl::parse_ddl;
        use super::types::SqlDialect;
        parse_ddl(ddl, SqlDialect::Generic)
    }

    /// Convenience method to load from a file path string.
    pub fn from_yaml_file(path: &str) -> Result<Self, SchemaLoadError> {
        Self::from_file(Path::new(path))
    }

    /// Validate the schema definition for internal consistency.
    fn validate(&self) -> Result<(), SchemaLoadError> {
        if self.tables.is_empty() {
            return Err(SchemaLoadError::ValidationError(
                "Schema must define at least one table".to_string(),
            ));
        }

        for (table_name, table_def) in &self.tables {
            if table_def.columns.is_empty() {
                return Err(SchemaLoadError::ValidationError(format!(
                    "Table '{table_name}' must have at least one column",
                )));
            }

            // Validate primary key columns exist
            if let Some(pk_cols) = &table_def.primary_key {
                for pk_col in pk_cols {
                    if !table_def.columns.iter().any(|c| &c.name == pk_col) {
                        return Err(SchemaLoadError::ValidationError(format!(
                            "Primary key column '{pk_col}' not found in table '{table_name}'",
                        )));
                    }
                }
            }

            // Validate foreign key columns exist
            for fk in &table_def.foreign_keys {
                for fk_col in &fk.columns {
                    if !table_def.columns.iter().any(|c| &c.name == fk_col) {
                        return Err(SchemaLoadError::ValidationError(format!(
                            "Foreign key column '{fk_col}' not found in table '{table_name}'",
                        )));
                    }
                }
                // Validate referenced table exists
                if !self.tables.contains_key(&fk.references.table) {
                    let ref_table = &fk.references.table;
                    return Err(SchemaLoadError::ValidationError(format!(
                        "Foreign key in '{table_name}' references non-existent table '{ref_table}'",
                    )));
                }
            }
        }

        Ok(())
    }
}

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

    #[test]
    fn test_load_yaml_basic() {
        let yaml = r#"
version: "1"
dialect: generic
tables:
  users:
    columns:
      - name: id
        type: INT
        nullable: false
      - name: name
        type: VARCHAR
"#;
        let schema = SchemaDefinition::from_yaml(yaml).unwrap();
        assert_eq!(schema.tables.len(), 1);
        assert!(schema.tables.contains_key("users"));
        assert_eq!(schema.tables["users"].columns.len(), 2);
    }

    #[test]
    fn test_load_yaml_empty_tables_fails() {
        let yaml = r#"
version: "1"
tables: {}
"#;
        let result = SchemaDefinition::from_yaml(yaml);
        assert!(result.is_err());
    }

    #[test]
    fn test_load_json_basic() {
        let json = r#"{
  "version": "1",
  "tables": {
    "users": {
      "columns": [
        {"name": "id", "type": "INT", "nullable": false}
      ]
    }
  }
}"#;
        let schema = SchemaDefinition::from_json(json).unwrap();
        assert_eq!(schema.tables.len(), 1);
    }
}
