//! Comprehensive tests for the test generation module.

use altimate_core::schema::types::{ColumnDef, SchemaDefinition, SqlDialect, TableDef};
use altimate_core::testgen::engine::generate_tests;
use altimate_core::testgen::types::TestExpectation;
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", false),
                col("name", "VARCHAR", true),
                col("email", "VARCHAR", true),
                col("age", "INT", true),
                col("is_active", "BOOLEAN", false),
                col("balance", "DECIMAL(10,2)", true),
                col("created_at", "TIMESTAMP", false),
            ],
            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", false),
                col("user_id", "INT", false),
                col("total", "DECIMAL(10,2)", true),
                col("status", "VARCHAR", true),
            ],
            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 col(name: &str, dt: &str, nullable: bool) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: dt.to_string(),
        nullable,
        description: None,
        tags: vec![],
        synonyms: vec![],

        statistics: None,
    }
}

fn categories(r: &altimate_core::testgen::types::TestGenResult) -> Vec<String> {
    r.test_cases.iter().map(|tc| tc.category.clone()).collect()
}

// ─── Basic generation ────────────────────────────────────────────

#[test]
fn test_generates_test_cases() {
    let r = generate_tests("SELECT id, name FROM users", &test_schema());
    assert!(!r.test_cases.is_empty());
}

#[test]
fn test_preserves_original_sql() {
    let sql = "SELECT id FROM users";
    let r = generate_tests(sql, &test_schema());
    assert_eq!(r.sql, sql);
}

#[test]
fn test_tables_referenced() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    assert!(r.tables_referenced.contains(&"users".to_string()));
}

#[test]
fn test_columns_analyzed() {
    let r = generate_tests("SELECT id, name FROM users", &test_schema());
    assert!(r.columns_analyzed.contains(&"id".to_string()));
    assert!(r.columns_analyzed.contains(&"name".to_string()));
}

// ─── Boundary value tests ────────────────────────────────────────

#[test]
fn test_boundary_tests_for_int_column() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    let boundary_tests: Vec<_> = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "boundary" && tc.name.contains("id"))
        .collect();
    assert!(!boundary_tests.is_empty());
}

#[test]
fn test_boundary_tests_include_zero() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "boundary" && tc.inputs.iter().any(|i| i.value == "0"))
    );
}

#[test]
fn test_boundary_tests_include_max() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    assert!(
        r.test_cases.iter().any(
            |tc| tc.category == "boundary" && tc.inputs.iter().any(|i| i.value == "2147483647")
        )
    );
}

#[test]
fn test_boundary_tests_for_string_column() {
    let r = generate_tests("SELECT name FROM users", &test_schema());
    let boundary = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "boundary" && tc.name.contains("name"))
        .count();
    assert!(boundary >= 1); // empty string, long string
}

#[test]
fn test_boundary_tests_include_empty_string() {
    let r = generate_tests("SELECT name FROM users", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "boundary" && tc.inputs.iter().any(|i| i.value == "''"))
    );
}

#[test]
fn test_boundary_tests_for_boolean_column() {
    let r = generate_tests("SELECT is_active FROM users", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "boundary" && tc.inputs.iter().any(|i| i.value == "TRUE"))
    );
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "boundary" && tc.inputs.iter().any(|i| i.value == "FALSE"))
    );
}

// ─── NULL handling tests ─────────────────────────────────────────

#[test]
fn test_null_tests_for_nullable_columns() {
    let r = generate_tests("SELECT name FROM users", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "null" && tc.name.contains("name"))
    );
}

#[test]
fn test_no_null_tests_for_non_nullable_columns() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    // id is NOT NULL, so no null test should be generated for it
    assert!(
        !r.test_cases
            .iter()
            .any(|tc| tc.category == "null" && tc.name == "null_id")
    );
}

#[test]
fn test_null_test_has_null_value() {
    let r = generate_tests("SELECT name FROM users", &test_schema());
    let null_test = r
        .test_cases
        .iter()
        .find(|tc| tc.category == "null" && tc.name.contains("name"));
    assert!(null_test.is_some());
    assert!(null_test.unwrap().inputs.iter().any(|i| i.value == "NULL"));
}

// ─── Empty table tests ──────────────────────────────────────────

#[test]
fn test_empty_table_test_generated() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    let cats = categories(&r);
    assert!(cats.contains(&"empty".to_string()));
}

#[test]
fn test_empty_table_expects_empty_result() {
    let r = generate_tests("SELECT id FROM users", &test_schema());
    let empty_test = r.test_cases.iter().find(|tc| tc.category == "empty");
    assert!(matches!(
        empty_test.unwrap().expected,
        TestExpectation::ReturnsEmpty
    ));
}

// ─── Type edge case tests ────────────────────────────────────────

#[test]
fn test_decimal_type_edge_tests() {
    let r = generate_tests("SELECT balance FROM users", &test_schema());
    let edge_tests: Vec<_> = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "type_edge" && tc.name.contains("balance"))
        .collect();
    assert!(!edge_tests.is_empty());
}

#[test]
fn test_timestamp_type_edge_tests() {
    let r = generate_tests("SELECT created_at FROM users", &test_schema());
    let edge_tests: Vec<_> = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "type_edge" && tc.name.contains("created_at"))
        .collect();
    assert!(!edge_tests.is_empty());
}

// ─── WHERE clause tests ─────────────────────────────────────────

#[test]
fn test_where_clause_generates_tests() {
    let r = generate_tests("SELECT id FROM users WHERE age > 18", &test_schema());
    let where_tests: Vec<_> = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "where_clause")
        .collect();
    assert!(!where_tests.is_empty());
}

#[test]
fn test_where_clause_match_test() {
    let r = generate_tests("SELECT id FROM users WHERE age = 25", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "where_clause" && tc.name.contains("match"))
    );
}

#[test]
fn test_where_clause_no_match_test() {
    let r = generate_tests("SELECT id FROM users WHERE age = 25", &test_schema());
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.category == "where_clause" && tc.name.contains("no_match"))
    );
}

// ─── JOIN tests ──────────────────────────────────────────────────

#[test]
fn test_join_generates_join_tests() {
    let r = generate_tests(
        "SELECT u.name, o.total FROM users u JOIN orders o ON u.id = o.user_id",
        &test_schema(),
    );
    let join_tests: Vec<_> = r
        .test_cases
        .iter()
        .filter(|tc| tc.category == "join")
        .collect();
    assert!(!join_tests.is_empty());
}

#[test]
fn test_join_matching_rows_test() {
    let r = generate_tests(
        "SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id",
        &test_schema(),
    );
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.name == "join_matching_rows")
    );
}

#[test]
fn test_join_no_matching_rows_test() {
    let r = generate_tests(
        "SELECT u.name FROM users u JOIN orders o ON u.id = o.user_id",
        &test_schema(),
    );
    assert!(
        r.test_cases
            .iter()
            .any(|tc| tc.name == "join_no_matching_rows")
    );
}

// ─── Wildcard expansion ─────────────────────────────────────────

#[test]
fn test_wildcard_expands_columns() {
    let r = generate_tests("SELECT * FROM users", &test_schema());
    assert!(r.columns_analyzed.contains(&"id".to_string()));
    assert!(r.columns_analyzed.contains(&"name".to_string()));
}

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

#[test]
fn test_invalid_sql_returns_empty() {
    let r = generate_tests("THIS IS NOT SQL", &test_schema());
    assert!(r.test_cases.is_empty());
}

// ─── Test case names ─────────────────────────────────────────────

#[test]
fn test_all_test_cases_have_names() {
    let r = generate_tests("SELECT id, name FROM users WHERE age > 18", &test_schema());
    for tc in &r.test_cases {
        assert!(!tc.name.is_empty());
    }
}

#[test]
fn test_all_test_cases_have_descriptions() {
    let r = generate_tests("SELECT id, name FROM users WHERE age > 18", &test_schema());
    for tc in &r.test_cases {
        assert!(!tc.description.is_empty());
    }
}

#[test]
fn test_all_test_cases_have_categories() {
    let r = generate_tests("SELECT id, name FROM users WHERE age > 18", &test_schema());
    for tc in &r.test_cases {
        assert!(!tc.category.is_empty());
    }
}
