//! Comprehensive tests for the context window optimizer module.

use altimate_core::context::engine::{
    optimize_context, optimize_for_query, schema_fingerprint, schema_summary,
};
use altimate_core::context::types::{ContextConfig, ContextResult, DisclosureLevel};
use altimate_core::schema::types::{
    ColumnDef, ForeignKeyDef, ForeignKeyRef, SchemaDefinition, SqlDialect, TableDef,
};
use std::collections::HashMap;

const LARGE_SCHEMA_YAML: &str = include_str!("../../../tests/fixtures/schemas/large_schema.yaml");

// ── Test helpers ──

fn col(name: &str, dtype: &str, nullable: bool, desc: Option<&str>, tags: Vec<&str>) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: dtype.to_string(),
        nullable,
        description: desc.map(|s| s.to_string()),
        tags: tags.into_iter().map(|s| s.to_string()).collect(),
        synonyms: vec![],

        statistics: None,
    }
}

fn make_ecommerce_schema() -> SchemaDefinition {
    let mut tables = HashMap::new();

    tables.insert(
        "users".to_string(),
        TableDef {
            description: Some("Core user accounts".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, Some("User ID"), vec![]),
                col("email", "VARCHAR", false, Some("Email"), vec!["pii"]),
                col("name", "VARCHAR", true, Some("Full name"), vec!["pii"]),
                col("status", "VARCHAR", false, None, vec![]),
                col("created_at", "TIMESTAMP", false, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    tables.insert(
        "orders".to_string(),
        TableDef {
            description: Some("Customer orders".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, None, vec![]),
                col("user_id", "INT", false, Some("FK to users.id"), vec![]),
                col("total_amount", "DECIMAL(10,2)", false, None, vec![]),
                col("status", "VARCHAR", false, None, vec![]),
                col("created_at", "TIMESTAMP", false, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![ForeignKeyDef {
                columns: vec!["user_id".to_string()],
                references: ForeignKeyRef {
                    table: "users".to_string(),
                    columns: vec!["id".to_string()],
                },
            }],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    tables.insert(
        "products".to_string(),
        TableDef {
            description: Some("Product catalog".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, None, vec![]),
                col("name", "VARCHAR", false, None, vec![]),
                col("price", "DECIMAL(10,2)", false, None, vec![]),
                col("category", "VARCHAR", true, None, vec![]),
                col("is_active", "BOOLEAN", false, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    tables.insert(
        "order_items".to_string(),
        TableDef {
            description: Some("Line items within orders".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, None, vec![]),
                col("order_id", "INT", false, None, vec![]),
                col("product_id", "INT", false, None, vec![]),
                col("quantity", "INT", false, None, vec![]),
                col("unit_price", "DECIMAL(10,2)", false, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![
                ForeignKeyDef {
                    columns: vec!["order_id".to_string()],
                    references: ForeignKeyRef {
                        table: "orders".to_string(),
                        columns: vec!["id".to_string()],
                    },
                },
                ForeignKeyDef {
                    columns: vec!["product_id".to_string()],
                    references: ForeignKeyRef {
                        table: "products".to_string(),
                        columns: vec!["id".to_string()],
                    },
                },
            ],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    tables.insert(
        "payments".to_string(),
        TableDef {
            description: Some("Payment transactions".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, None, vec![]),
                col("order_id", "INT", false, None, vec![]),
                col("amount", "DECIMAL(10,2)", false, None, vec![]),
                col("method", "VARCHAR", false, None, vec![]),
                col("status", "VARCHAR", false, None, vec![]),
                col("gateway_ref", "VARCHAR", true, None, vec!["secret"]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![ForeignKeyDef {
                columns: vec!["order_id".to_string()],
                references: ForeignKeyRef {
                    table: "orders".to_string(),
                    columns: vec!["id".to_string()],
                },
            }],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    tables.insert(
        "categories".to_string(),
        TableDef {
            description: Some("Product categories".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT", false, None, vec![]),
                col("name", "VARCHAR", false, None, vec![]),
                col("parent_id", "INT", true, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );

    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    }
}

fn make_empty_schema() -> SchemaDefinition {
    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    }
}

fn make_single_table_schema() -> SchemaDefinition {
    let mut tables = HashMap::new();
    tables.insert(
        "metrics".to_string(),
        TableDef {
            description: Some("Application metrics".to_string()),
            database: None,
            schema: None,
            columns: vec![
                col("id", "BIGINT", false, None, vec![]),
                col("name", "VARCHAR", false, None, vec![]),
                col("value", "FLOAT", false, None, vec![]),
                col("timestamp", "TIMESTAMP", false, None, vec![]),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Snowflake,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    }
}

/// Generate a schema with `n` tables, each with 5 columns.
fn make_large_schema(n: usize) -> SchemaDefinition {
    let mut tables = HashMap::new();
    for i in 0..n {
        let name = format!("table_{i:03}");
        tables.insert(
            name.clone(),
            TableDef {
                description: Some(format!("Auto-generated table {i}")),
                database: None,
                schema: None,
                columns: vec![
                    col("id", "INT", false, None, vec![]),
                    col(&format!("col_a_{i}"), "VARCHAR", true, None, vec![]),
                    col(&format!("col_b_{i}"), "INT", true, None, vec![]),
                    col(&format!("col_c_{i}"), "DECIMAL(10,2)", true, None, vec![]),
                    col("created_at", "TIMESTAMP", false, None, vec![]),
                ],
                primary_key: Some(vec!["id".to_string()]),
                foreign_keys: vec![],
                statistics: None,

                clustering_keys: vec![],
            },
        );
    }
    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables,
        database: None,
        schema_name: None,
        glossary: None,
    }
}

// ════════════════════════════════════════════════════════════════════════
// 1. Fingerprint tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_fingerprint_deterministic() {
    let schema = make_ecommerce_schema();
    let fp1 = schema_fingerprint(&schema);
    let fp2 = schema_fingerprint(&schema);
    assert_eq!(fp1, fp2, "Fingerprint must be deterministic");
}

#[test]
fn test_fingerprint_hex_format() {
    let schema = make_ecommerce_schema();
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64, "SHA-256 should produce 64 hex chars");
    assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
}

#[test]
fn test_fingerprint_changes_on_add_table() {
    let schema1 = make_ecommerce_schema();
    let mut schema2 = make_ecommerce_schema();
    schema2.tables.insert(
        "new_table".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![col("id", "INT", false, None, vec![])],
            primary_key: None,
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    assert_ne!(schema_fingerprint(&schema1), schema_fingerprint(&schema2));
}

#[test]
fn test_fingerprint_changes_on_add_column() {
    let schema1 = make_ecommerce_schema();
    let mut schema2 = make_ecommerce_schema();
    schema2.tables.get_mut("users").unwrap().columns.push(col(
        "phone",
        "VARCHAR",
        true,
        None,
        vec!["pii"],
    ));
    assert_ne!(schema_fingerprint(&schema1), schema_fingerprint(&schema2));
}

#[test]
fn test_fingerprint_empty_schema() {
    let schema = make_empty_schema();
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64);
}

// ════════════════════════════════════════════════════════════════════════
// 2. Schema summary tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_summary_includes_all_tables() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    assert_eq!(summary.table_summaries.len(), 6);
}

#[test]
fn test_summary_header_contains_table_count() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("6 tables"));
}

#[test]
fn test_summary_includes_domain() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(
        summary.natural_language.contains("e-commerce"),
        "Should detect e-commerce domain: {}",
        summary.natural_language
    );
}

#[test]
fn test_summary_includes_relationships() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("Key relationships"));
    assert!(
        summary
            .natural_language
            .contains("orders.user_id -> users.id")
    );
}

#[test]
fn test_summary_table_descriptions() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    let users = summary
        .table_summaries
        .iter()
        .find(|t| t.name == "users")
        .unwrap();
    assert_eq!(users.description, "Core user accounts");
    assert_eq!(users.column_count, 5);
}

#[test]
fn test_summary_key_columns_include_pk_and_fk() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    let orders = summary
        .table_summaries
        .iter()
        .find(|t| t.name == "orders")
        .unwrap();
    assert!(orders.key_columns.contains(&"id".to_string()));
    assert!(orders.key_columns.contains(&"user_id".to_string()));
}

#[test]
fn test_summary_estimated_tokens_positive() {
    let schema = make_ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(summary.estimated_tokens > 0);
}

#[test]
fn test_summary_empty_schema() {
    let schema = make_empty_schema();
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("0 tables"));
    assert!(summary.table_summaries.is_empty());
}

#[test]
fn test_summary_single_table_singular() {
    let schema = make_single_table_schema();
    let summary = schema_summary(&schema);
    assert!(
        summary.natural_language.contains("1 table "),
        "Should use singular: {}",
        summary.natural_language
    );
}

// ════════════════════════════════════════════════════════════════════════
// 3. Disclosure level tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_disclosure_level_ordering() {
    assert!(DisclosureLevel::Fingerprint < DisclosureLevel::TableList);
    assert!(DisclosureLevel::TableList < DisclosureLevel::ColumnNames);
    assert!(DisclosureLevel::ColumnNames < DisclosureLevel::FullRelevant);
    assert!(DisclosureLevel::FullRelevant < DisclosureLevel::Full);
}

#[test]
fn test_disclosure_level_next_chain() {
    let mut level = DisclosureLevel::Fingerprint;
    let mut count = 0;
    while let Some(next) = level.next() {
        level = next;
        count += 1;
    }
    assert_eq!(count, 4);
    assert_eq!(level, DisclosureLevel::Full);
}

#[test]
fn test_disclosure_level_prev_chain() {
    let mut level = DisclosureLevel::Full;
    let mut count = 0;
    while let Some(prev) = level.prev() {
        level = prev;
        count += 1;
    }
    assert_eq!(count, 4);
    assert_eq!(level, DisclosureLevel::Fingerprint);
}

#[test]
fn test_disclosure_level_display() {
    assert_eq!(format!("{}", DisclosureLevel::Fingerprint), "fingerprint");
    assert_eq!(format!("{}", DisclosureLevel::TableList), "table_list");
    assert_eq!(format!("{}", DisclosureLevel::ColumnNames), "column_names");
    assert_eq!(
        format!("{}", DisclosureLevel::FullRelevant),
        "full_relevant"
    );
    assert_eq!(format!("{}", DisclosureLevel::Full), "full");
}

#[test]
fn test_disclosure_level_as_u8() {
    assert_eq!(DisclosureLevel::Fingerprint.as_u8(), 0);
    assert_eq!(DisclosureLevel::TableList.as_u8(), 1);
    assert_eq!(DisclosureLevel::ColumnNames.as_u8(), 2);
    assert_eq!(DisclosureLevel::FullRelevant.as_u8(), 3);
    assert_eq!(DisclosureLevel::Full.as_u8(), 4);
}

// ════════════════════════════════════════════════════════════════════════
// 4. optimize_context tests — each disclosure level
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_optimize_fingerprint_level() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Fingerprint,
        max_tokens: 100,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::Fingerprint);
    assert!(r.compressed_schema.contains("schema_fingerprint"));
    assert!(r.compressed_schema.contains("tables: 6"));
    assert_eq!(r.tables_total, 6);
}

#[test]
fn test_optimize_table_list_level() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::TableList,
        max_tokens: 10000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::TableList);
    assert!(r.compressed_schema.contains("users"));
    assert!(r.compressed_schema.contains("orders"));
    assert!(r.compressed_schema.contains("5 cols"));
    // Should include descriptions when enabled (default)
    assert!(r.compressed_schema.contains("Core user accounts"));
}

#[test]
fn test_optimize_column_names_level() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::ColumnNames,
        max_tokens: 10000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::ColumnNames);
    assert!(r.compressed_schema.contains("columns: ["));
    assert!(r.compressed_schema.contains("email"));
    assert!(r.compressed_schema.contains("total_amount"));
}

#[test]
fn test_optimize_full_level() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::Full);
    assert!(r.compressed_schema.contains("INT"));
    assert!(r.compressed_schema.contains("VARCHAR"));
    assert!(r.compressed_schema.contains("DECIMAL(10,2)"));
    assert!(r.compressed_schema.contains("primary_key:"));
}

#[test]
fn test_optimize_full_relevant_no_query() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    // Without query context, no tables are "relevant" unless priority_tables set
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::FullRelevant);
    // All tables should appear but in compact form (no types)
    assert!(r.compressed_schema.contains("users: ["));
}

// ════════════════════════════════════════════════════════════════════════
// 5. Auto-stepdown tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_auto_stepdown_from_full_to_lower() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 10, // impossibly small
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(r.level_used < DisclosureLevel::Full);
    assert!(r.estimated_tokens <= 10 || r.level_used == DisclosureLevel::Fingerprint);
}

#[test]
fn test_auto_stepdown_bottoms_out_at_fingerprint() {
    let schema = make_large_schema(100);
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 1, // impossibly small
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.level_used, DisclosureLevel::Fingerprint);
}

#[test]
fn test_stepdown_preserves_fingerprint() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 5,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.fingerprint.len(), 64);
    assert_eq!(r.fingerprint, schema_fingerprint(&schema));
}

// ════════════════════════════════════════════════════════════════════════
// 6. Compression ratio tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_compression_ratio_table_list_vs_full() {
    let schema = make_ecommerce_schema();
    let table_list = optimize_context(
        &schema,
        &ContextConfig {
            level: DisclosureLevel::TableList,
            max_tokens: 100000,
            ..Default::default()
        },
    )
    .unwrap();
    let full = optimize_context(
        &schema,
        &ContextConfig {
            level: DisclosureLevel::Full,
            max_tokens: 100000,
            ..Default::default()
        },
    )
    .unwrap();
    assert!(
        table_list.compression_ratio < full.compression_ratio,
        "TableList ({:.2}) should compress more than Full ({:.2})",
        table_list.compression_ratio,
        full.compression_ratio
    );
}

#[test]
fn test_compression_ratio_bounded() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::ColumnNames,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(r.compression_ratio >= 0.0);
    assert!(r.compression_ratio <= 1.0);
}

// ════════════════════════════════════════════════════════════════════════
// 7. optimize_for_query tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_query_optimization_single_table() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_for_query("SELECT id, email FROM users", &schema, &config).unwrap();
    assert!(r.compressed_schema.contains("users"));
    assert_eq!(r.tables_total, 6);
}

#[test]
fn test_query_optimization_join() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_for_query(
        "SELECT u.name, o.total_amount FROM users u JOIN orders o ON u.id = o.user_id",
        &schema,
        &config,
    )
    .unwrap();
    assert!(r.compressed_schema.contains("users"));
    assert!(r.compressed_schema.contains("orders"));
}

#[test]
fn test_query_optimization_fk_expansion() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    // Querying orders should expand to include users via FK
    let r = optimize_for_query("SELECT * FROM orders", &schema, &config).unwrap();
    assert!(r.compressed_schema.contains("orders"));
    assert!(r.compressed_schema.contains("users"));
}

#[test]
fn test_query_optimization_priority_tables() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        priority_tables: vec!["payments".to_string()],
        ..Default::default()
    };
    let r = optimize_for_query("SELECT id FROM users", &schema, &config).unwrap();
    // payments not in query but should be included as priority
    assert!(r.compressed_schema.contains("payments"));
}

#[test]
fn test_query_optimization_no_match() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_for_query("SELECT 1 + 1", &schema, &config).unwrap();
    assert_eq!(r.tables_total, 6);
    // All tables in compact form
    assert!(r.compressed_schema.contains("users"));
}

#[test]
fn test_query_optimization_auto_stepdown() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 5,
        ..Default::default()
    };
    let r = optimize_for_query("SELECT * FROM users", &schema, &config).unwrap();
    assert!(r.level_used < DisclosureLevel::Full);
}

// ════════════════════════════════════════════════════════════════════════
// 8. Config options tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_config_default() {
    let config = ContextConfig::default();
    assert_eq!(config.max_tokens, 2000);
    assert_eq!(config.level, DisclosureLevel::ColumnNames);
    assert!(config.include_descriptions);
    assert!(!config.include_tags);
    assert!(config.include_foreign_keys);
    assert!(config.priority_tables.is_empty());
}

#[test]
fn test_config_no_descriptions() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        include_descriptions: false,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(!r.compressed_schema.contains("Core user accounts"));
    assert!(!r.compressed_schema.contains("# Email"));
}

#[test]
fn test_config_with_tags() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        include_tags: true,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(r.compressed_schema.contains("[pii]"));
}

#[test]
fn test_config_without_tags() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        include_tags: false,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(!r.compressed_schema.contains("[pii]"));
}

#[test]
fn test_config_no_foreign_keys() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        include_foreign_keys: false,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(!r.compressed_schema.contains("foreign_keys:"));
}

#[test]
fn test_config_with_foreign_keys() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        include_foreign_keys: true,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(r.compressed_schema.contains("foreign_keys:"));
    assert!(r.compressed_schema.contains("users.id"));
}

// ════════════════════════════════════════════════════════════════════════
// 9. Large schema tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_large_schema_fingerprint() {
    let schema = make_large_schema(60);
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64);
}

#[test]
fn test_large_schema_summary() {
    let schema = make_large_schema(60);
    let summary = schema_summary(&schema);
    assert_eq!(summary.table_summaries.len(), 60);
    assert!(summary.natural_language.contains("60 tables"));
}

#[test]
fn test_large_schema_compression_effective() {
    let schema = make_large_schema(60);
    let config = ContextConfig {
        level: DisclosureLevel::TableList,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(
        r.compression_ratio < 0.5,
        "TableList should compress 60 tables significantly, got ratio {:.2}",
        r.compression_ratio
    );
}

#[test]
fn test_large_schema_column_names_within_budget() {
    let schema = make_large_schema(60);
    let config = ContextConfig {
        level: DisclosureLevel::ColumnNames,
        max_tokens: 5000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(r.estimated_tokens <= 5000);
}

#[test]
fn test_large_schema_auto_stepdown() {
    let schema = make_large_schema(60);
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 500, // very small for 60 tables
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    // Should step down from Full because 60 tables with full detail exceeds 500 tokens
    assert!(r.level_used < DisclosureLevel::Full);
}

// ════════════════════════════════════════════════════════════════════════
// 10. YAML fixture loading tests
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_load_large_schema_fixture() {
    let schema: SchemaDefinition =
        serde_yaml::from_str(LARGE_SCHEMA_YAML).expect("should parse large schema");
    assert!(
        schema.tables.len() >= 50,
        "Large schema should have 50+ tables, got {}",
        schema.tables.len()
    );
}

#[test]
fn test_large_fixture_fingerprint() {
    let schema: SchemaDefinition = serde_yaml::from_str(LARGE_SCHEMA_YAML).unwrap();
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64);
}

#[test]
fn test_large_fixture_summary() {
    let schema: SchemaDefinition = serde_yaml::from_str(LARGE_SCHEMA_YAML).unwrap();
    let summary = schema_summary(&schema);
    assert!(summary.table_summaries.len() >= 50);
    assert!(summary.estimated_tokens > 0);
}

#[test]
fn test_large_fixture_optimize_all_levels() {
    let schema: SchemaDefinition = serde_yaml::from_str(LARGE_SCHEMA_YAML).unwrap();

    let levels = [
        DisclosureLevel::Fingerprint,
        DisclosureLevel::TableList,
        DisclosureLevel::ColumnNames,
        DisclosureLevel::Full,
    ];

    let mut prev_tokens = 0;
    for level in &levels {
        let config = ContextConfig {
            level: *level,
            max_tokens: 1_000_000,
            ..Default::default()
        };
        let r = optimize_context(&schema, &config).unwrap();
        assert_eq!(r.level_used, *level);
        assert!(
            r.estimated_tokens >= prev_tokens,
            "Higher levels should use more tokens: {:?} ({}) vs prev ({})",
            level,
            r.estimated_tokens,
            prev_tokens
        );
        prev_tokens = r.estimated_tokens;
    }
}

#[test]
fn test_large_fixture_compression_ratio_monotonic() {
    let schema: SchemaDefinition = serde_yaml::from_str(LARGE_SCHEMA_YAML).unwrap();

    let levels = [
        DisclosureLevel::Fingerprint,
        DisclosureLevel::TableList,
        DisclosureLevel::ColumnNames,
        DisclosureLevel::Full,
    ];

    let mut prev_ratio = 0.0_f64;
    for level in &levels {
        let config = ContextConfig {
            level: *level,
            max_tokens: 1_000_000,
            ..Default::default()
        };
        let r = optimize_context(&schema, &config).unwrap();
        assert!(
            r.compression_ratio >= prev_ratio,
            "Compression ratio should increase with detail: {:?} ({:.4}) vs prev ({:.4})",
            level,
            r.compression_ratio,
            prev_ratio
        );
        prev_ratio = r.compression_ratio;
    }
}

// ════════════════════════════════════════════════════════════════════════
// 11. Edge cases
// ════════════════════════════════════════════════════════════════════════

#[test]
fn test_empty_schema_all_levels() {
    let schema = make_empty_schema();
    for level in [
        DisclosureLevel::Fingerprint,
        DisclosureLevel::TableList,
        DisclosureLevel::ColumnNames,
        DisclosureLevel::FullRelevant,
        DisclosureLevel::Full,
    ] {
        let config = ContextConfig {
            level,
            max_tokens: 100000,
            ..Default::default()
        };
        let r = optimize_context(&schema, &config).unwrap();
        assert_eq!(r.tables_total, 0);
        assert_eq!(r.tables_included, 0);
    }
}

#[test]
fn test_single_table_schema() {
    let schema = make_single_table_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert_eq!(r.tables_total, 1);
    assert_eq!(r.tables_included, 1);
    assert!(r.compressed_schema.contains("metrics"));
}

#[test]
fn test_context_result_serializable() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig::default();
    let r = optimize_context(&schema, &config).unwrap();
    let json = serde_json::to_string(&r).expect("ContextResult should be serializable");
    let parsed: ContextResult =
        serde_json::from_str(&json).expect("ContextResult should be deserializable");
    assert_eq!(parsed.level_used, r.level_used);
    assert_eq!(parsed.estimated_tokens, r.estimated_tokens);
}

#[test]
fn test_context_config_serializable() {
    let config = ContextConfig {
        max_tokens: 1500,
        level: DisclosureLevel::TableList,
        include_descriptions: false,
        include_tags: true,
        include_foreign_keys: false,
        priority_tables: vec!["users".to_string()],
    };
    let json = serde_json::to_string(&config).expect("ContextConfig should be serializable");
    let parsed: ContextConfig =
        serde_json::from_str(&json).expect("ContextConfig should be deserializable");
    assert_eq!(parsed.max_tokens, 1500);
    assert_eq!(parsed.level, DisclosureLevel::TableList);
    assert!(!parsed.include_descriptions);
    assert!(parsed.include_tags);
}

#[test]
fn test_query_with_invalid_sql_still_works() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 100000,
        ..Default::default()
    };
    // Invalid SQL — extract_tables will return empty, optimizer should still work
    let r = optimize_for_query("NOT VALID SQL !!!", &schema, &config).unwrap();
    assert_eq!(r.tables_total, 6);
}

#[test]
fn test_nullable_indicator_in_full_output() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 100000,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    // Nullable columns get '?' suffix
    assert!(
        r.compressed_schema.contains("VARCHAR?"),
        "Nullable VARCHAR columns should have '?' suffix"
    );
}

#[test]
fn test_fk_in_column_names_level() {
    let schema = make_ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::ColumnNames,
        max_tokens: 100000,
        include_foreign_keys: true,
        ..Default::default()
    };
    let r = optimize_context(&schema, &config).unwrap();
    assert!(
        r.compressed_schema.contains("fk:"),
        "ColumnNames level should show FK info"
    );
}
