use super::{ObjectType, SchemaObject};
use std::collections::BTreeMap;
use std::fs;
use std::path::Path;

/// Write all objects grouped by schema - one file per schema
pub fn write_objects_by_schema(base_dir: &Path, objects: &[SchemaObject]) -> anyhow::Result<()> {
    let grouped = group_by_schema(objects);

    // Ensure base directory exists
    fs::create_dir_all(base_dir)?;

    for (schema_name, schema_objects) in grouped {
        let content = generate_schema_file(&schema_name, &schema_objects);
        let file_path = base_dir.join(format!("{}.sql", schema_name));
        fs::write(&file_path, content)?;
    }

    Ok(())
}

/// Group objects by schema name, preserving order within each schema
pub fn group_by_schema(objects: &[SchemaObject]) -> BTreeMap<String, Vec<&SchemaObject>> {
    let mut grouped: BTreeMap<String, Vec<&SchemaObject>> = BTreeMap::new();

    for obj in objects {
        grouped
            .entry(obj.schema_name.clone())
            .or_default()
            .push(obj);
    }

    // Sort objects within each schema by type then name
    for objects in grouped.values_mut() {
        objects.sort_by(|a, b| {
            let type_order = |t: ObjectType| -> u8 {
                match t {
                    ObjectType::Type => 0,
                    ObjectType::Sequence => 1,
                    ObjectType::Table => 2,
                    ObjectType::Index => 3,
                    ObjectType::Constraint => 4,
                    ObjectType::View => 5,
                    ObjectType::MaterializedView => 6,
                    ObjectType::Function => 7,
                    ObjectType::Trigger => 8,
                }
            };
            type_order(a.object_type)
                .cmp(&type_order(b.object_type))
                .then_with(|| a.object_name.cmp(&b.object_name))
        });
    }

    grouped
}

/// Generate a single SQL file content for a schema
pub fn generate_schema_file(schema_name: &str, objects: &[&SchemaObject]) -> String {
    let mut content = String::new();

    // Header
    content.push_str(&format!("-- Schema: {}\n", schema_name));
    content.push_str("-- Generated by pgcmp\n");
    content.push_str(&format!("-- Objects: {}\n", objects.len()));
    content.push_str("--\n\n");

    let mut current_type: Option<ObjectType> = None;

    for obj in objects {
        // Add section header when object type changes
        if current_type != Some(obj.object_type) {
            if current_type.is_some() {
                content.push('\n');
            }
            content.push_str(
                "-- ============================================================================\n",
            );
            content.push_str(&format!("-- {}\n", object_type_plural(obj.object_type)));
            content.push_str("-- ============================================================================\n\n");
            current_type = Some(obj.object_type);
        }

        // Add object header
        content.push_str(&format!(
            "-- {}: {}\n",
            object_type_label(obj.object_type),
            obj.object_name
        ));

        // Add DDL
        content.push_str(&obj.ddl);

        // Ensure DDL ends with newline
        if !obj.ddl.ends_with('\n') {
            content.push('\n');
        }
        content.push('\n');
    }

    content
}

/// Generate content for memfs (same as generate_schema_file but takes owned objects)
pub fn generate_schema_content(schema_name: &str, objects: Vec<SchemaObject>) -> String {
    let refs: Vec<&SchemaObject> = objects.iter().collect();
    generate_schema_file(schema_name, &refs)
}

fn object_type_label(t: ObjectType) -> &'static str {
    match t {
        ObjectType::Type => "Type",
        ObjectType::Table => "Table",
        ObjectType::View => "View",
        ObjectType::MaterializedView => "Materialized View",
        ObjectType::Function => "Function",
        ObjectType::Index => "Index",
        ObjectType::Constraint => "Constraint",
        ObjectType::Trigger => "Trigger",
        ObjectType::Sequence => "Sequence",
    }
}

fn object_type_plural(t: ObjectType) -> &'static str {
    match t {
        ObjectType::Type => "TYPES",
        ObjectType::Table => "TABLES",
        ObjectType::View => "VIEWS",
        ObjectType::MaterializedView => "MATERIALIZED VIEWS",
        ObjectType::Function => "FUNCTIONS",
        ObjectType::Index => "INDEXES",
        ObjectType::Constraint => "CONSTRAINTS",
        ObjectType::Trigger => "TRIGGERS",
        ObjectType::Sequence => "SEQUENCES",
    }
}
