//! Comprehensive tests for the query rewriter module.

use altimate_core::rewriter::engine::rewrite;
use altimate_core::schema::types::{ColumnDef, SchemaDefinition, SqlDialect, TableDef};
use std::collections::HashMap;

// ─── Helpers ─────────────────────────────────────────────────────

fn test_schema() -> SchemaDefinition {
    let mut tables = HashMap::new();
    tables.insert(
        "users".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT"),
                col("name", "VARCHAR"),
                col("email", "VARCHAR"),
            ],
            primary_key: Some(vec!["id".to_string()]),
            foreign_keys: vec![],
            statistics: None,

            clustering_keys: vec![],
        },
    );
    tables.insert(
        "orders".to_string(),
        TableDef {
            description: None,
            database: None,
            schema: None,
            columns: vec![
                col("id", "INT"),
                col("user_id", "INT"),
                col("total", "DECIMAL(10,2)"),
            ],
            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 empty_schema() -> SchemaDefinition {
    SchemaDefinition {
        version: "1".to_string(),
        dialect: SqlDialect::Generic,
        tables: HashMap::new(),
        database: None,
        schema_name: None,
        glossary: None,
    }
}

fn col(name: &str, dt: &str) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: dt.to_string(),
        nullable: false,
        description: None,
        tags: vec![],
        synonyms: vec![],

        statistics: None,
    }
}

fn rule_names(result: &altimate_core::rewriter::types::RewriteResult) -> Vec<String> {
    result.suggestions.iter().map(|s| s.rule.clone()).collect()
}

// ─── SELECT * expansion ─────────────────────────────────────────

#[test]
fn test_star_expansion_suggestion() {
    let r = rewrite("SELECT * FROM users", &test_schema());
    assert!(rule_names(&r).contains(&"star_to_columns".to_string()));
}

#[test]
fn test_star_expansion_lists_columns() {
    let r = rewrite("SELECT * FROM users", &test_schema());
    let suggestion = r.suggestions.iter().find(|s| s.rule == "star_to_columns");
    assert!(suggestion.is_some());
    let rewritten = &suggestion.unwrap().rewritten_sql;
    assert!(rewritten.contains("id"));
    assert!(rewritten.contains("name"));
    assert!(rewritten.contains("email"));
}

#[test]
fn test_star_expansion_no_suggestion_for_explicit_columns() {
    let r = rewrite("SELECT id, name FROM users", &test_schema());
    assert!(!rule_names(&r).contains(&"star_to_columns".to_string()));
}

#[test]
fn test_star_expansion_confidence() {
    let r = rewrite("SELECT * FROM users", &test_schema());
    let suggestion = r.suggestions.iter().find(|s| s.rule == "star_to_columns");
    assert!(suggestion.unwrap().confidence > 0.8);
}

#[test]
fn test_star_expansion_with_unknown_table() {
    let r = rewrite("SELECT * FROM unknown_table", &test_schema());
    // No expansion possible since table not in schema
    assert!(!rule_names(&r).contains(&"star_to_columns".to_string()));
}

// ─── Subquery to JOIN ────────────────────────────────────────────

#[test]
fn test_subquery_to_join_suggestion() {
    let r = rewrite(
        "SELECT id, name FROM users WHERE id IN (SELECT user_id FROM orders)",
        &test_schema(),
    );
    assert!(rule_names(&r).contains(&"subquery_to_join".to_string()));
}

#[test]
fn test_subquery_to_join_rewrite_contains_join() {
    let r = rewrite(
        "SELECT id, name FROM users WHERE id IN (SELECT user_id FROM orders)",
        &test_schema(),
    );
    let suggestion = r.suggestions.iter().find(|s| s.rule == "subquery_to_join");
    assert!(suggestion.is_some());
    let rewritten = &suggestion.unwrap().rewritten_sql;
    assert!(rewritten.to_uppercase().contains("JOIN"));
}

#[test]
fn test_subquery_to_join_confidence() {
    let r = rewrite(
        "SELECT id FROM users WHERE id IN (SELECT user_id FROM orders)",
        &test_schema(),
    );
    let suggestion = r.suggestions.iter().find(|s| s.rule == "subquery_to_join");
    assert!(suggestion.unwrap().confidence >= 0.7);
}

// ─── Redundant DISTINCT ─────────────────────────────────────────

#[test]
fn test_redundant_distinct_suggestion() {
    let r = rewrite(
        "SELECT DISTINCT name FROM users GROUP BY name",
        &test_schema(),
    );
    assert!(rule_names(&r).contains(&"redundant_distinct".to_string()));
}

#[test]
fn test_redundant_distinct_removes_distinct() {
    let r = rewrite(
        "SELECT DISTINCT name FROM users GROUP BY name",
        &test_schema(),
    );
    let suggestion = r
        .suggestions
        .iter()
        .find(|s| s.rule == "redundant_distinct");
    assert!(suggestion.is_some());
    // Rewritten should not contain DISTINCT
    let rewritten_upper = suggestion.unwrap().rewritten_sql.to_uppercase();
    assert!(!rewritten_upper.contains("DISTINCT"));
}

#[test]
fn test_no_redundant_distinct_for_non_distinct_query() {
    // A query without DISTINCT should never trigger redundant_distinct
    let r = rewrite("SELECT name FROM users GROUP BY name", &test_schema());
    assert!(!rule_names(&r).contains(&"redundant_distinct".to_string()));
}

// ─── Index suggestions ──────────────────────────────────────────

#[test]
fn test_where_clause_index_suggestion() {
    let r = rewrite(
        "SELECT * FROM users WHERE email = 'test@test.com'",
        &test_schema(),
    );
    assert!(!r.index_suggestions.is_empty());
    assert!(
        r.index_suggestions
            .iter()
            .any(|s| s.columns.contains(&"email".to_string()))
    );
}

#[test]
fn test_index_suggestion_has_ddl() {
    let r = rewrite(
        "SELECT * FROM users WHERE email = 'test@test.com'",
        &test_schema(),
    );
    for suggestion in &r.index_suggestions {
        assert!(suggestion.ddl.contains("CREATE INDEX"));
    }
}

#[test]
fn test_join_column_index_suggestion() {
    let r = rewrite(
        "SELECT u.name FROM users u INNER JOIN orders o ON u.id = o.user_id",
        &test_schema(),
    );
    assert!(
        r.index_suggestions
            .iter()
            .any(|s| s.reason.contains("JOIN"))
    );
}

#[test]
fn test_no_index_suggestion_for_simple_select() {
    let r = rewrite("SELECT id FROM users", &test_schema());
    // A simple SELECT without WHERE, JOIN, or ORDER BY should not generate index suggestions
    assert!(r.index_suggestions.is_empty());
}

#[test]
fn test_order_by_index_suggestion() {
    let r = rewrite("SELECT * FROM users ORDER BY name", &test_schema());
    assert!(
        r.index_suggestions
            .iter()
            .any(|s| s.reason.contains("ORDER BY"))
    );
}

// ─── Invalid SQL ─────────────────────────────────────────────────

#[test]
fn test_invalid_sql_returns_empty_suggestions() {
    let r = rewrite("THIS IS NOT SQL", &test_schema());
    assert!(r.suggestions.is_empty());
    assert!(r.index_suggestions.is_empty());
}

// ─── Original SQL preservation ───────────────────────────────────

#[test]
fn test_original_sql_preserved() {
    let sql = "SELECT * FROM users";
    let r = rewrite(sql, &test_schema());
    assert_eq!(r.original_sql, sql);
}

// ─── Clean queries ───────────────────────────────────────────────

#[test]
fn test_clean_query_no_suggestions() {
    let r = rewrite("SELECT id, name FROM users WHERE id = 1", &test_schema());
    assert!(r.suggestions.is_empty());
}

// ─── Multiple suggestions ────────────────────────────────────────

#[test]
fn test_multiple_suggestions_possible() {
    let r = rewrite(
        "SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)",
        &test_schema(),
    );
    // May get star_to_columns and subquery_to_join
    assert!(r.suggestions.len() >= 1);
}

// ─── Empty schema ────────────────────────────────────────────────

#[test]
fn test_empty_schema_no_star_expansion() {
    let r = rewrite("SELECT * FROM users", &empty_schema());
    assert!(!rule_names(&r).contains(&"star_to_columns".to_string()));
}

// ─── Improvement and explanation fields ──────────────────────────

#[test]
fn test_suggestions_have_explanation() {
    let r = rewrite("SELECT * FROM users", &test_schema());
    for s in &r.suggestions {
        assert!(!s.explanation.is_empty());
    }
}

#[test]
fn test_suggestions_have_improvement() {
    let r = rewrite("SELECT * FROM users", &test_schema());
    for s in &r.suggestions {
        assert!(!s.improvement.is_empty());
    }
}

#[test]
fn test_index_suggestions_have_table() {
    let r = rewrite("SELECT * FROM users WHERE id = 1", &test_schema());
    for s in &r.index_suggestions {
        assert!(!s.table.is_empty());
    }
}

#[test]
fn test_index_suggestions_have_reason() {
    let r = rewrite("SELECT * FROM users WHERE id = 1", &test_schema());
    for s in &r.index_suggestions {
        assert!(!s.reason.is_empty());
    }
}
