//! Integration tests for the query equivalence module.
//!
//! Tests cover equivalence checking, regression detection, and
//! dialect-specific syntax variations.

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")
}

fn schema_with_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
}

fn schema_with_table_removed() -> SchemaDefinition {
    let mut schema = ecommerce_schema();
    schema.tables.remove("order_items");
    schema
}

// ═══════════════════════════════════════════════════════════════════════
// Identical queries — should be equivalent
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_identical_simple_select() {
    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.8);
    assert!(result.output_compatible);
}

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

#[tokio::test]
async fn equiv_identical_aggregation() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT user_id, SUM(total_amount) FROM orders GROUP BY user_id",
        "SELECT user_id, SUM(total_amount) FROM orders GROUP BY user_id",
        &schema,
    )
    .await;
    assert!(result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Minor differences — ordering only
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_with_vs_without_order_by() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users WHERE age > 25",
        "SELECT id FROM users WHERE age > 25 ORDER BY id",
        &schema,
    )
    .await;
    // Ordering difference is minor
    if !result.differences.is_empty() {
        let ordering = result.differences.iter().find(|d| d.aspect == "ordering");
        if let Some(ord) = ordering {
            assert_eq!(ord.severity, DiffSeverity::Minor);
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════
// Structural differences — different tables
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_different_tables_not_equivalent() {
    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"));
}

#[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.equivalent);
    assert!(!result.output_compatible);
}

// ═══════════════════════════════════════════════════════════════════════
// Filter differences
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_different_where_clause() {
    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"));
}

#[tokio::test]
async fn equiv_with_vs_without_filter() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users",
        "SELECT id FROM users WHERE age > 25",
        &schema,
    )
    .await;
    // Filter difference is semantic (not structural), so the engine may
    // consider them "equivalent" at the heuristic threshold. Key assertion:
    // the filter difference is detected.
    assert!(result.differences.iter().any(|d| d.aspect == "filters"));
}

// ═══════════════════════════════════════════════════════════════════════
// Aggregation differences
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_one_with_aggregation_one_without() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT user_id, total_amount FROM orders",
        "SELECT user_id, SUM(total_amount) FROM orders GROUP BY user_id",
        &schema,
    )
    .await;
    assert!(!result.equivalent);
}

// ═══════════════════════════════════════════════════════════════════════
// Invalid queries
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn equiv_invalid_query_a() {
    let schema = ecommerce_schema();
    let result = check_equivalence("SELECTZ broken", "SELECT id 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 broken", "SELECTZ broken2", &schema).await;
    assert!(!result.equivalent);
    assert!(result.validation_errors.len() >= 2);
}

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

#[tokio::test]
async fn equiv_regression_column_removed() {
    let old_schema = ecommerce_schema();
    let new_schema = schema_with_column_removed();
    let result = check_regression("SELECT age FROM users", &old_schema, &new_schema).await;
    assert!(!result.passes);
    assert!(!result.breaking_changes.is_empty());
}

#[tokio::test]
async fn equiv_regression_table_removed() {
    let old_schema = ecommerce_schema();
    let new_schema = schema_with_table_removed();
    let result = check_regression("SELECT * FROM order_items", &old_schema, &new_schema).await;
    assert!(!result.passes);
}

#[tokio::test]
async fn equiv_regression_no_change() {
    let schema = ecommerce_schema();
    let result = check_regression("SELECT id FROM users", &schema, &schema).await;
    assert!(result.passes);
    assert!(result.breaking_changes.is_empty());
}

#[tokio::test]
async fn equiv_regression_already_invalid() {
    let schema = ecommerce_schema();
    let result = check_regression("SELECTZ broken", &schema, &schema).await;
    assert!(!result.passes);
    assert!(!result.validation_errors.is_empty());
}

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

#[tokio::test]
async fn equiv_result_serializes() {
    let schema = ecommerce_schema();
    let result = check_equivalence("SELECT id FROM users", "SELECT id FROM users", &schema).await;
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("equivalent"));
    assert!(json.contains("confidence"));
}

#[tokio::test]
async fn equiv_regression_result_serializes() {
    let schema = ecommerce_schema();
    let result = check_regression("SELECT id FROM users", &schema, &schema).await;
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("passes"));
}

// ═══════════════════════════════════════════════════════════════════════
// DiffSeverity display
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn equiv_diff_severity_display() {
    assert_eq!(DiffSeverity::Structural.to_string(), "structural");
    assert_eq!(DiffSeverity::Semantic.to_string(), "semantic");
    assert_eq!(DiffSeverity::Minor.to_string(), "minor");
}

// ═══════════════════════════════════════════════════════════════════════
// LIMIT differences
// ═══════════════════════════════════════════════════════════════════════

#[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;
    // LIMIT difference is semantic, so engine may still consider them "equivalent"
    // at the heuristic threshold. Key assertion: the limit difference is detected.
    assert!(result.differences.iter().any(|d| d.aspect == "limit"));
}

#[tokio::test]
async fn equiv_with_vs_without_limit() {
    let schema = ecommerce_schema();
    let result = check_equivalence(
        "SELECT id FROM users",
        "SELECT id FROM users LIMIT 10",
        &schema,
    )
    .await;
    // Having LIMIT vs not is a semantic difference
    assert!(result.differences.iter().any(|d| d.aspect == "limit"));
}
