//! Integration tests for the context window optimizer module.
//!
//! Tests cover optimize_context, optimize_for_query, schema_fingerprint,
//! schema_summary, disclosure levels, token estimation, and config variants.

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

fn ecommerce_schema() -> SchemaDefinition {
    SchemaDefinition::from_yaml(include_str!(
        "../../../tests/fixtures/schemas/ecommerce.yaml"
    ))
    .expect("Failed to load ecommerce schema")
}

fn make_col(name: &str, data_type: &str, tags: Vec<&str>) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: data_type.to_string(),
        nullable: true,
        description: None,
        tags: tags.into_iter().map(|s| s.to_string()).collect(),
        synonyms: vec![],

        statistics: None,
    }
}

fn large_schema() -> SchemaDefinition {
    let mut tables = HashMap::new();
    for i in 0..20 {
        let cols: Vec<ColumnDef> = (0..10)
            .map(|j| make_col(&format!("col_{j}"), "VARCHAR", vec![]))
            .collect();
        tables.insert(
            format!("table_{i}"),
            TableDef {
                description: Some(format!("Table number {i}")),
                database: None,
                schema: None,
                columns: cols,
                primary_key: Some(vec!["col_0".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,
    }
}

// ═══════════════════════════════════════════════════════════════════════
// schema_fingerprint
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_fingerprint_deterministic() {
    let schema = ecommerce_schema();
    let fp1 = schema_fingerprint(&schema);
    let fp2 = schema_fingerprint(&schema);
    assert_eq!(fp1, fp2);
}

#[test]
fn context_fingerprint_hex_64_chars() {
    let schema = ecommerce_schema();
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64);
    assert!(fp.chars().all(|c| c.is_ascii_hexdigit()));
}

#[test]
fn context_fingerprint_changes_on_modification() {
    let schema1 = ecommerce_schema();
    let mut schema2 = ecommerce_schema();
    schema2.tables.insert(
        "extra_table".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![make_col("id", "INT", vec![])],
            primary_key: None,
            foreign_keys: vec![],
            statistics: None,

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

#[test]
fn context_fingerprint_empty_schema() {
    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    };
    let fp = schema_fingerprint(&schema);
    assert_eq!(fp.len(), 64);
}

// ═══════════════════════════════════════════════════════════════════════
// schema_summary
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_summary_includes_all_tables() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    assert_eq!(summary.table_summaries.len(), schema.tables.len());
}

#[test]
fn context_summary_natural_language_header() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("4 tables"));
}

#[test]
fn context_summary_includes_relationships() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("Key relationships"));
}

#[test]
fn context_summary_table_descriptions() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    let users = summary
        .table_summaries
        .iter()
        .find(|t| t.name == "users")
        .expect("should find users");
    assert!(!users.description.is_empty());
    assert_eq!(users.column_count, schema.tables["users"].columns.len());
}

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

#[test]
fn context_summary_empty_schema() {
    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    };
    let summary = schema_summary(&schema);
    assert!(summary.natural_language.contains("0 tables"));
    assert!(summary.table_summaries.is_empty());
}

#[test]
fn context_summary_domain_inference() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    // Ecommerce schema should be detected as e-commerce
    assert!(
        summary.natural_language.contains("e-commerce")
            || summary.natural_language.contains("user management")
    );
}

// ═══════════════════════════════════════════════════════════════════════
// DisclosureLevel
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_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 context_disclosure_level_next() {
    assert_eq!(
        DisclosureLevel::Fingerprint.next(),
        Some(DisclosureLevel::TableList)
    );
    assert_eq!(
        DisclosureLevel::TableList.next(),
        Some(DisclosureLevel::ColumnNames)
    );
    assert_eq!(DisclosureLevel::Full.next(), None);
}

#[test]
fn context_disclosure_level_prev() {
    assert_eq!(DisclosureLevel::Fingerprint.prev(), None);
    assert_eq!(
        DisclosureLevel::Full.prev(),
        Some(DisclosureLevel::FullRelevant)
    );
}

#[test]
fn context_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);
}

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

// ═══════════════════════════════════════════════════════════════════════
// optimize_context — different levels
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_optimize_fingerprint_level() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Fingerprint,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.level_used, DisclosureLevel::Fingerprint);
    assert!(result.compressed_schema.contains("schema_fingerprint"));
    assert_eq!(result.tables_total, 4);
}

#[test]
fn context_optimize_table_list_level() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::TableList,
        max_tokens: 5000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.level_used, DisclosureLevel::TableList);
    assert!(result.compressed_schema.contains("users"));
    assert!(result.compressed_schema.contains("orders"));
}

#[test]
fn context_optimize_column_names_level() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::ColumnNames,
        max_tokens: 5000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.level_used, DisclosureLevel::ColumnNames);
    assert!(result.compressed_schema.contains("email"));
}

#[test]
fn context_optimize_full_level() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.level_used, DisclosureLevel::Full);
    assert!(result.compressed_schema.contains("INT"));
    assert!(result.compressed_schema.contains("VARCHAR"));
}

// ═══════════════════════════════════════════════════════════════════════
// Auto-stepdown
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_optimize_auto_stepdown_small_budget() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 10, // Very small budget forces stepdown
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert!(result.level_used < DisclosureLevel::Full);
}

#[test]
fn context_optimize_auto_stepdown_large_schema() {
    let schema = large_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50, // Very small budget for 20 tables
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert!(result.level_used <= DisclosureLevel::Fingerprint);
}

// ═══════════════════════════════════════════════════════════════════════
// Compression ratio and metadata
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_optimize_compression_ratio() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::TableList,
        max_tokens: 5000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert!(result.compression_ratio > 0.0);
    assert!(result.compression_ratio < 1.0);
}

#[test]
fn context_optimize_has_fingerprint() {
    let schema = ecommerce_schema();
    let config = ContextConfig::default();
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.fingerprint.len(), 64);
}

#[test]
fn context_optimize_token_estimate() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert!(result.estimated_tokens > 0);
    assert!(result.estimated_tokens <= config.max_tokens);
}

#[test]
fn context_optimize_tables_included() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.tables_included, 4);
    assert_eq!(result.tables_total, 4);
}

// ═══════════════════════════════════════════════════════════════════════
// optimize_for_query
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_optimize_for_query_single_table() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 5000,
        ..Default::default()
    };
    let result =
        optimize_for_query("SELECT id FROM users", &schema, &config).expect("should optimize");
    assert!(result.compressed_schema.contains("users"));
    assert!(result.tables_included <= result.tables_total);
}

#[test]
fn context_optimize_for_query_with_join() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 5000,
        ..Default::default()
    };
    let result = optimize_for_query(
        "SELECT u.name, o.total_amount FROM users u JOIN orders o ON u.id = o.user_id",
        &schema,
        &config,
    )
    .expect("should optimize");
    assert!(result.compressed_schema.contains("users"));
    assert!(result.compressed_schema.contains("orders"));
}

#[test]
fn context_optimize_for_query_priority_tables() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 5000,
        priority_tables: vec!["products".to_string()],
        ..Default::default()
    };
    let result =
        optimize_for_query("SELECT id FROM users", &schema, &config).expect("should optimize");
    // Products included even though not in query
    assert!(result.compressed_schema.contains("products"));
}

#[test]
fn context_optimize_for_query_no_tables_referenced() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::FullRelevant,
        max_tokens: 5000,
        ..Default::default()
    };
    let result = optimize_for_query("SELECT 1 + 1", &schema, &config).expect("should optimize");
    assert_eq!(result.tables_total, 4);
}

// ═══════════════════════════════════════════════════════════════════════
// Config variants
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_default_config() {
    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 context_config_without_descriptions() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        include_descriptions: false,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    // Table descriptions from the schema should not appear
    // Check for a known description
    assert!(!result.compressed_schema.contains("Core user accounts"));
}

#[test]
fn context_config_with_tags() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        include_tags: true,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    // The ecommerce schema has "pii" tags
    assert!(result.compressed_schema.contains("[pii"));
}

#[test]
fn context_config_without_foreign_keys() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        include_foreign_keys: false,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert!(!result.compressed_schema.contains("foreign_keys:"));
}

// ═══════════════════════════════════════════════════════════════════════
// Empty schema
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_optimize_empty_schema() {
    let schema = SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    };
    let config = ContextConfig::default();
    let result = optimize_context(&schema, &config).expect("should optimize");
    assert_eq!(result.tables_total, 0);
    assert_eq!(result.tables_included, 0);
}

// ═══════════════════════════════════════════════════════════════════════
// Serialization
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn context_result_serializes() {
    let schema = ecommerce_schema();
    let config = ContextConfig {
        level: DisclosureLevel::Full,
        max_tokens: 50000,
        ..Default::default()
    };
    let result = optimize_context(&schema, &config).expect("should optimize");
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("compressed_schema"));
    assert!(json.contains("level_used"));
    assert!(json.contains("estimated_tokens"));
    assert!(json.contains("fingerprint"));
}

#[test]
fn context_config_serializes() {
    let config = ContextConfig::default();
    let json = serde_json::to_string(&config).expect("should serialize");
    assert!(json.contains("max_tokens"));
    assert!(json.contains("level"));
}

#[test]
fn context_summary_serializes() {
    let schema = ecommerce_schema();
    let summary = schema_summary(&schema);
    let json = serde_json::to_string(&summary).expect("should serialize");
    assert!(json.contains("natural_language"));
    assert!(json.contains("table_summaries"));
    assert!(json.contains("estimated_tokens"));
}
