//! Comprehensive tests for the query equivalence module.
//!
//! Tests cover equivalence checking, regression checking, and
//! edge cases with Snowflake, BigQuery, Databricks, and dbt-style SQL.

use altimate_core::equivalence::engine::{check_equivalence, check_regression};
use altimate_core::equivalence::types::DiffSeverity;
use altimate_core::schema::types::SchemaDefinition;

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

/// Build a modified version of the ecommerce schema (column removed).
fn ecommerce_schema_v2_column_removed() -> SchemaDefinition {
    let mut schema = ecommerce_schema();
    if let Some(users) = schema.tables.get_mut("users") {
        users.columns.retain(|c| c.name != "age");
    }
    schema
}

/// Build a modified version with a table removed.
fn ecommerce_schema_v2_table_removed() -> SchemaDefinition {
    let mut schema = ecommerce_schema();
    schema.tables.remove("order_items");
    schema
}

/// Build a modified version with column renamed.
fn ecommerce_schema_v2_column_renamed() -> SchemaDefinition {
    let mut schema = ecommerce_schema();
    if let Some(users) = schema.tables.get_mut("users") {
        if let Some(col) = users.columns.iter_mut().find(|c| c.name == "email") {
            col.name = "email_address".to_string();
        }
    }
    schema
}

// ═══════════════════════════════════════════════════════════════════════
// Identical queries
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_identical_simple_select() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id, email FROM users",
        "SELECT id, email FROM users",
        &schema,
    )
    .await;
    assert!(result.equivalent);
    assert!(result.confidence > 0.9);
    assert!(result.output_compatible);
    assert!(result.differences.is_empty());
}

#[tokio::test]
async fn equiv_identical_with_where() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users WHERE age > 25",
        "SELECT id FROM users WHERE age > 25",
        &schema,
    )
    .await;
    assert!(result.equivalent);
    assert!(result.confidence > 0.9);
}

#[tokio::test]
async fn equiv_identical_with_join() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

#[tokio::test]
async fn equiv_identical_aggregate() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT status, COUNT(*) FROM users GROUP BY status",
        "SELECT status, COUNT(*) FROM users GROUP BY status",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Queries that differ only in ORDER BY (minor difference)
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_same_query_different_order() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id, email FROM users WHERE age > 25",
        "SELECT id, email FROM users WHERE age > 25 ORDER BY id",
        &schema,
    )
    .await;
    // Should be close to equivalent — only ordering differs
    assert!(result.output_compatible);
    // The sort difference is minor
    let has_minor = result
        .differences
        .iter()
        .any(|d| d.severity == DiffSeverity::Minor);
    if !result.differences.is_empty() {
        assert!(has_minor);
    }
}

// ═══════════════════════════════════════════════════════════════════════
// Queries with structural differences
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_different_tables() {
    let schema = ecommerce_schema();
    let result = check_equivalence("SELECT id FROM users", "SELECT id FROM orders", &schema).await;
    assert!(!result.equivalent);
    assert!(
        result.differences.iter().any(|d| d.aspect == "tables"),
        "Expected table difference, got: {:?}",
        result.differences
    );
}

#[tokio::test]
async fn equiv_different_columns() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id, email FROM users",
        "SELECT id, name FROM users",
        &schema,
    )
    .await;
    // Different columns selected — should have column name differences
    assert!(
        !result.differences.is_empty(),
        "Expected differences for different column selections"
    );
    assert!(
        result.differences.iter().any(|d| d.aspect == "columns"),
        "Expected column name differences"
    );
}

#[tokio::test]
async fn equiv_different_column_count() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users",
        "SELECT id, email FROM users",
        &schema,
    )
    .await;
    assert!(!result.output_compatible);
    assert!(!result.equivalent);
}

#[tokio::test]
async fn equiv_different_filters() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users WHERE age > 25",
        "SELECT id FROM users WHERE age > 30",
        &schema,
    )
    .await;
    assert!(!result.equivalent);
    assert!(
        result.differences.iter().any(|d| d.aspect == "filters"),
        "Expected filter difference"
    );
}

#[tokio::test]
async fn equiv_different_join_count() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT u.id FROM users u",
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        &schema,
    )
    .await;
    assert!(!result.equivalent);
}

#[tokio::test]
async fn equiv_one_has_aggregation() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users",
        "SELECT COUNT(*) FROM users",
        &schema,
    )
    .await;
    assert!(!result.equivalent);
    assert!(
        result.differences.iter().any(|d| d.aspect == "aggregation"),
        "Expected aggregation difference"
    );
}

#[tokio::test]
async fn equiv_different_limit() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users LIMIT 10",
        "SELECT id FROM users LIMIT 20",
        &schema,
    )
    .await;
    // Different LIMIT values — should detect a difference
    // Note: this may show up as a limit diff or the queries may still be
    // considered equivalent if limit extraction doesn't match
    assert!(
        !result.differences.is_empty() || !result.equivalent,
        "Expected some difference for different LIMIT values"
    );
}

// ═══════════════════════════════════════════════════════════════════════
// Validation failures
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_invalid_query_a() {
    let schema = ecommerce_schema();
    let result =
        check_equivalence("SELECT * FROM nonexistent", "SELECT * FROM users", &schema).await;
    assert!(!result.equivalent);
    assert!(!result.validation_errors.is_empty());
}

#[tokio::test]
async fn equiv_invalid_query_b() {
    let schema = ecommerce_schema();
    let result = check_equivalence("SELECT * FROM users", "SELECTZ * FROM users", &schema).await;
    assert!(!result.equivalent);
    assert!(!result.validation_errors.is_empty());
}

#[tokio::test]
async fn equiv_both_invalid() {
    let schema = ecommerce_schema();
    let result = check_equivalence("SELECTZ * FROM users", "SELECTZ * FROM orders", &schema).await;
    assert!(!result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Regression checking
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn regression_no_change() {
    let schema = ecommerce_schema();
    let result = check_regression(
        "SELECT id, email FROM users WHERE age > 25",
        &schema,
        &schema, // Same schema
    )
    .await;
    assert!(result.passes);
    assert!(result.breaking_changes.is_empty());
}

#[tokio::test]
async fn regression_column_removed() {
    let old_schema = ecommerce_schema();
    let new_schema = ecommerce_schema_v2_column_removed(); // age removed
    let result = check_regression("SELECT id, age FROM users", &old_schema, &new_schema).await;
    assert!(!result.passes);
    assert!(
        !result.breaking_changes.is_empty(),
        "Expected breaking changes for removed column"
    );
}

#[tokio::test]
async fn regression_table_removed() {
    let old_schema = ecommerce_schema();
    let new_schema = ecommerce_schema_v2_table_removed(); // order_items removed
    let result = check_regression("SELECT * FROM order_items", &old_schema, &new_schema).await;
    assert!(!result.passes);
    assert!(
        result
            .breaking_changes
            .iter()
            .any(|c| c.change_type == "table_removed")
    );
}

#[tokio::test]
async fn regression_column_renamed() {
    let old_schema = ecommerce_schema();
    let new_schema = ecommerce_schema_v2_column_renamed(); // email -> email_address
    let result = check_regression("SELECT id, email FROM users", &old_schema, &new_schema).await;
    assert!(!result.passes);
    assert!(!result.breaking_changes.is_empty());
}

#[tokio::test]
async fn regression_unaffected_query() {
    let old_schema = ecommerce_schema();
    let new_schema = ecommerce_schema_v2_column_removed(); // age removed
    // This query doesn't use 'age', so it should still work
    let result = check_regression("SELECT id, email FROM users", &old_schema, &new_schema).await;
    assert!(result.passes);
}

#[tokio::test]
async fn regression_already_invalid() {
    let old_schema = ecommerce_schema();
    let new_schema = ecommerce_schema_v2_column_removed();
    let result =
        check_regression("SELECT * FROM nonexistent_table", &old_schema, &new_schema).await;
    // Query was already invalid against old schema
    assert!(!result.passes);
    assert!(!result.validation_errors.is_empty());
}

// ═══════════════════════════════════════════════════════════════════════
// Complex query equivalence
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_cte_vs_subquery() {
    let schema = ecommerce_schema();
    // CTE version
    let sql_a = "WITH active AS (SELECT * FROM users WHERE status = 'active') \
                 SELECT id FROM active";
    // Subquery version
    let sql_b = "SELECT id FROM (SELECT * FROM users WHERE status = 'active') AS active";
    let result = check_equivalence(sql_a, sql_b, &schema).await;
    // These should produce the same output
    assert!(result.output_compatible);
}

#[tokio::test]
async fn equiv_different_join_types() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        "SELECT u.id FROM users u LEFT JOIN orders o ON u.id = o.user_id",
        &schema,
    )
    .await;
    assert!(!result.equivalent);
}

#[tokio::test]
async fn equiv_window_functions() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id, ROW_NUMBER() OVER (ORDER BY created_at) as rn FROM users",
        "SELECT id, ROW_NUMBER() OVER (ORDER BY created_at) as rn FROM users",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Snowflake patterns
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_snowflake_variant_queries() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT u.id, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id",
        "SELECT u.id, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.id",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// BigQuery patterns
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_bigquery_count_patterns() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT COUNT(*) FROM users WHERE status = 'active'",
        "SELECT COUNT(*) FROM users WHERE status = 'active'",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// dbt patterns
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_dbt_model_refactor() {
    let schema = ecommerce_schema();
    // Before and after refactoring a dbt model
    let sql_a = "SELECT id, email, name, status FROM users WHERE status = 'active'";
    let sql_b = "SELECT id, email, name, status FROM users WHERE status = 'active'";
    let result = check_equivalence(sql_a, sql_b, &schema).await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Edge cases
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_select_star_vs_named_columns() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT * FROM users",
        "SELECT id, email, name, age, status, created_at, updated_at FROM users",
        &schema,
    )
    .await;
    // SELECT * expands to all columns — should be equivalent
    assert!(result.output_compatible);
}

#[tokio::test]
async fn equiv_same_union() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users UNION ALL SELECT id FROM orders",
        "SELECT id FROM users UNION ALL SELECT id FROM orders",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}
