//! Multi-dialect compatibility tests for altimate-core.
//!
//! Tests validate SQL queries from fixture files against dialect-specific schemas.
//! Each dialect has a suite of tests for validation, explanation, and error handling.
//! Covers Snowflake, Postgres, BigQuery, Redshift, Databricks, and dbt schemas.

use altimate_core::errors::types::{ErrorCode, SuggestionKind};
use altimate_core::explainer::engine::explain;
use altimate_core::explainer::types::PlanOperation;
use altimate_core::fixer::engine::fix;
use altimate_core::fixer::types::FixConfig;
use altimate_core::schema::types::SchemaDefinition;
use altimate_core::validate;
use std::path::Path;

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

fn load_schema(name: &str) -> SchemaDefinition {
    let path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests/fixtures/schemas")
        .join(name);
    SchemaDefinition::from_file(&path)
        .unwrap_or_else(|e| panic!("Failed to load schema {}: {}", name, e))
}

fn load_sql(path: &str) -> String {
    let full_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests/fixtures/queries")
        .join(path);
    std::fs::read_to_string(&full_path)
        .unwrap_or_else(|e| panic!("Failed to load SQL {}: {}", path, e))
}

/// Check if plan steps contain an operation matching a predicate.
fn has_operation(
    steps: &[altimate_core::explainer::types::PlanStep],
    pred: &dyn Fn(&PlanOperation) -> bool,
) -> bool {
    steps.iter().any(|s| {
        if pred(&s.operation) {
            return true;
        }
        if let PlanOperation::Subquery { inner_steps, .. } = &s.operation {
            return has_operation(inner_steps, pred);
        }
        false
    })
}

// ─── Snowflake Tests ─────────────────────────────────────────────────

mod snowflake_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("snowflake_analytics.yaml")
    }

    #[tokio::test]
    async fn test_window_functions_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/window_functions.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "window_functions should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_window_functions_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/window_functions.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(!explanation.summary.is_empty());
        assert!(!explanation.plan_steps.is_empty());
        assert!(
            explanation.cost_signals.has_window_function,
            "Should detect window functions"
        );
    }

    #[tokio::test]
    async fn test_cte_with_aggregation_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/cte_with_aggregation.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "cte_with_aggregation should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_cte_with_aggregation_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/cte_with_aggregation.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(explanation.cost_signals.has_aggregation);
        assert!(
            has_operation(&explanation.plan_steps, &|op| matches!(
                op,
                PlanOperation::Subquery { .. }
            )),
            "CTE should produce subquery step"
        );
    }

    #[tokio::test]
    async fn test_multi_join_analytics_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/multi_join_analytics.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "multi_join_analytics should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_multi_join_analytics_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/multi_join_analytics.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 2,
            "Expected at least 2 joins, got {}",
            explanation.cost_signals.join_count
        );
        assert!(
            explanation.lineage.tables.len() >= 3,
            "Expected at least 3 tables in lineage"
        );
    }

    #[tokio::test]
    async fn test_subquery_exists_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/subquery_exists.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "subquery_exists should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_subquery_exists_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/subquery_exists.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(!explanation.plan_steps.is_empty());
    }

    #[tokio::test]
    async fn test_case_when_complex_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/case_when_complex.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "case_when_complex should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_case_when_complex_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/case_when_complex.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            has_operation(
                &explanation.plan_steps,
                &|op| matches!(op, PlanOperation::TableScan { table, .. } if table == "dim_users")
            ),
            "Should scan dim_users"
        );
    }

    #[tokio::test]
    async fn test_union_reporting_validates() {
        let schema = schema();
        let sql = load_sql("snowflake/union_reporting.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "union_reporting should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_union_reporting_explains() {
        let schema = schema();
        let sql = load_sql("snowflake/union_reporting.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(explanation.cost_signals.has_union, "Should detect UNION");
        assert!(
            has_operation(&explanation.plan_steps, &|op| matches!(
                op,
                PlanOperation::Union { .. }
            )),
            "Should have Union plan step"
        );
    }
}

// ─── Postgres Tests ──────────────────────────────────────────────────

mod postgres_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("postgres_app.yaml")
    }

    #[tokio::test]
    async fn test_recursive_cte_validates() {
        let schema = schema();
        let sql = load_sql("postgres/recursive_cte.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "recursive_cte should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_recursive_cte_explains() {
        let schema = schema();
        let sql = load_sql("postgres/recursive_cte.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(!explanation.plan_steps.is_empty());
        assert!(!explanation.summary.is_empty());
    }

    #[tokio::test]
    async fn test_json_operations_validates() {
        let schema = schema();
        let sql = load_sql("postgres/json_operations.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "json_operations should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_json_operations_explains() {
        let schema = schema();
        let sql = load_sql("postgres/json_operations.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.lineage.tables.len() >= 2,
            "Join query should reference at least 2 tables"
        );
    }

    #[tokio::test]
    async fn test_insert_returning_validates() {
        let schema = schema();
        let sql = load_sql("postgres/insert_returning.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "insert_returning should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_insert_returning_explains() {
        let schema = schema();
        let sql = load_sql("postgres/insert_returning.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 1,
            "Should have at least 1 join"
        );
    }

    #[tokio::test]
    async fn test_complex_analytics_validates() {
        let schema = schema();
        let sql = load_sql("postgres/complex_analytics.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "complex_analytics should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_complex_analytics_explains() {
        let schema = schema();
        let sql = load_sql("postgres/complex_analytics.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.has_aggregation,
            "Complex analytics should use aggregation"
        );
        assert!(
            has_operation(&explanation.plan_steps, &|op| matches!(
                op,
                PlanOperation::Subquery { .. }
            )),
            "CTE should produce subquery step"
        );
    }
}

// ─── BigQuery Tests ──────────────────────────────────────────────────

mod bigquery_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("bigquery_marketing.yaml")
    }

    #[tokio::test]
    async fn test_date_functions_validates() {
        let schema = schema();
        let sql = load_sql("bigquery/date_functions.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "date_functions should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_date_functions_explains() {
        let schema = schema();
        let sql = load_sql("bigquery/date_functions.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(explanation.cost_signals.has_aggregation);
        assert!(has_operation(
            &explanation.plan_steps,
            &|op| matches!(op, PlanOperation::TableScan { table, .. } if table == "ga4_events")
        ));
    }

    #[tokio::test]
    async fn test_marketing_attribution_validates() {
        let schema = schema();
        let sql = load_sql("bigquery/marketing_attribution.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "marketing_attribution should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_marketing_attribution_explains() {
        let schema = schema();
        let sql = load_sql("bigquery/marketing_attribution.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.has_window_function,
            "Marketing attribution uses RANK() window function"
        );
    }

    #[tokio::test]
    async fn test_customer_ltv_validates() {
        let schema = schema();
        let sql = load_sql("bigquery/customer_ltv.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "customer_ltv should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_customer_ltv_explains() {
        let schema = schema();
        let sql = load_sql("bigquery/customer_ltv.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(explanation.cost_signals.has_aggregation);
    }
}

// ─── Redshift Tests ──────────────────────────────────────────────────

mod redshift_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("redshift_warehouse.yaml")
    }

    #[tokio::test]
    async fn test_star_schema_query_validates() {
        let schema = schema();
        let sql = load_sql("redshift/star_schema_query.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "star_schema_query should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_star_schema_query_explains() {
        let schema = schema();
        let sql = load_sql("redshift/star_schema_query.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 2,
            "Star schema query should have at least 2 joins, got {}",
            explanation.cost_signals.join_count
        );
        assert!(
            explanation.lineage.tables.len() >= 3,
            "Star schema query should reference at least 3 tables"
        );
    }

    #[tokio::test]
    async fn test_customer_cohort_validates() {
        let schema = schema();
        let sql = load_sql("redshift/customer_cohort.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "customer_cohort should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_customer_cohort_explains() {
        let schema = schema();
        let sql = load_sql("redshift/customer_cohort.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            has_operation(&explanation.plan_steps, &|op| matches!(
                op,
                PlanOperation::Subquery { .. }
            )),
            "Cohort CTE should produce subquery step"
        );
    }
}

// ─── Databricks Tests ────────────────────────────────────────────────

mod databricks_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("databricks_lakehouse.yaml")
    }

    #[tokio::test]
    async fn test_medallion_query_validates() {
        let schema = schema();
        let sql = load_sql("databricks/medallion_query.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "medallion_query should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_medallion_query_explains() {
        let schema = schema();
        let sql = load_sql("databricks/medallion_query.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 1,
            "Medallion query should join silver+gold layers"
        );
        assert!(
            has_operation(&explanation.plan_steps, &|op| matches!(
                op,
                PlanOperation::Subquery { .. }
            )),
            "CTE should produce subquery step"
        );
    }

    #[tokio::test]
    async fn test_funnel_analysis_validates() {
        let schema = schema();
        let sql = load_sql("databricks/funnel_analysis.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "funnel_analysis should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_funnel_analysis_explains() {
        let schema = schema();
        let sql = load_sql("databricks/funnel_analysis.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            has_operation(
                &explanation.plan_steps,
                &|op| matches!(op, PlanOperation::TableScan { table, .. } if table == "gold_funnel_metrics")
            ),
            "Should scan gold_funnel_metrics"
        );
    }
}

// ─── dbt Tests ───────────────────────────────────────────────────────

mod dbt_tests {
    use super::*;

    fn schema() -> SchemaDefinition {
        load_schema("dbt_ecommerce.yaml")
    }

    #[tokio::test]
    async fn test_staging_model_validates() {
        let schema = schema();
        let sql = load_sql("dbt/staging_model.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "staging_model should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_staging_model_explains() {
        let schema = schema();
        let sql = load_sql("dbt/staging_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(!explanation.plan_steps.is_empty());
        assert!(
            has_operation(
                &explanation.plan_steps,
                &|op| matches!(op, PlanOperation::TableScan { table, .. } if table == "stg_orders")
            ),
            "Staging model should scan stg_orders"
        );
    }

    #[tokio::test]
    async fn test_intermediate_model_validates() {
        let schema = schema();
        let sql = load_sql("dbt/intermediate_model.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "intermediate_model should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_intermediate_model_explains() {
        let schema = schema();
        let sql = load_sql("dbt/intermediate_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.has_aggregation,
            "Intermediate model should have aggregation"
        );
    }

    #[tokio::test]
    async fn test_intermediate_model_output_columns() {
        let schema = schema();
        let sql = load_sql("dbt/intermediate_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        // The intermediate model selects: order_id, customer_id, order_date, status,
        // amount, payment_count, payment_method — 7 columns
        assert!(
            explanation.lineage.columns_output.len() >= 5,
            "Intermediate model should output at least 5 columns, got {}",
            explanation.lineage.columns_output.len()
        );
    }

    #[tokio::test]
    async fn test_mart_customers_validates() {
        let schema = schema();
        let sql = load_sql("dbt/mart_customers.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "mart_customers should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_mart_customers_explains() {
        let schema = schema();
        let sql = load_sql("dbt/mart_customers.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 2,
            "Mart customers should have at least 2 joins (customers+orders+payments), got {}",
            explanation.cost_signals.join_count
        );
    }

    #[tokio::test]
    async fn test_mart_customers_join_lineage() {
        let schema = schema();
        let sql = load_sql("dbt/mart_customers.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let lineage = &result.explanation.expect("should have explanation").lineage;
        // Should reference stg_customers, orders (or stg_orders), stg_payments
        assert!(
            lineage.tables.len() >= 3,
            "Mart customers should reference at least 3 tables, got {} ({:?})",
            lineage.tables.len(),
            lineage.tables
        );
    }

    #[tokio::test]
    async fn test_incremental_model_validates() {
        let schema = schema();
        let sql = load_sql("dbt/incremental_model.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "incremental_model should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_incremental_model_explains() {
        let schema = schema();
        let sql = load_sql("dbt/incremental_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.join_count >= 2,
            "Incremental model joins 3 tables, expected at least 2 joins"
        );
    }

    #[tokio::test]
    async fn test_snapshot_query_validates() {
        let schema = schema();
        let sql = load_sql("dbt/snapshot_query.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "snapshot_query should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_snapshot_query_explains() {
        let schema = schema();
        let sql = load_sql("dbt/snapshot_query.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(!explanation.plan_steps.is_empty());
    }

    #[tokio::test]
    async fn test_complex_dbt_model_validates() {
        let schema = schema();
        let sql = load_sql("dbt/complex_dbt_model.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            result.valid,
            "complex_dbt_model should be valid: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_complex_dbt_model_explains() {
        let schema = schema();
        let sql = load_sql("dbt/complex_dbt_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.has_window_function,
            "Complex dbt model uses ROW_NUMBER/SUM window functions"
        );
    }

    #[tokio::test]
    async fn test_complex_dbt_model_plan_steps() {
        let schema = schema();
        let sql = load_sql("dbt/complex_dbt_model.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        // Complex model with 3 CTEs and multiple joins should produce substantial plan
        assert!(
            explanation.plan_steps.len() >= 4,
            "Complex dbt model should have at least 4 plan steps, got {}",
            explanation.plan_steps.len()
        );
    }
}

// ─── Error Quality Tests ─────────────────────────────────────────────

mod error_quality_tests {
    use super::*;

    #[tokio::test]
    async fn test_typo_table_is_invalid() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.valid, "typo_table should be invalid");
    }

    #[tokio::test]
    async fn test_typo_table_has_e001() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        assert_eq!(
            result.errors[0].code,
            ErrorCode::E001,
            "Typo table should produce E001, got {:?}: {}",
            result.errors[0].code,
            result.errors[0].message
        );
    }

    #[tokio::test]
    async fn test_typo_table_has_suggestion() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        let error = &result.errors[0];
        // "dim_usrs" should suggest "dim_users"
        assert!(
            !error.suggestions.is_empty(),
            "Typo 'dim_usrs' should produce suggestions"
        );
        let has_dim_users = error.suggestions.iter().any(|s| match &s.kind {
            SuggestionKind::DidYouMean { name } => name.contains("dim_users"),
            _ => false,
        });
        assert!(
            has_dim_users,
            "Should suggest 'dim_users' for 'dim_usrs', got: {:?}",
            error.suggestions
        );
    }

    #[tokio::test]
    async fn test_typo_column_is_invalid() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/typo_column.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.valid, "typo_column should be invalid");
    }

    #[tokio::test]
    async fn test_typo_column_has_e002() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/typo_column.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        assert_eq!(
            result.errors[0].code,
            ErrorCode::E002,
            "Typo column should produce E002, got {:?}: {}",
            result.errors[0].code,
            result.errors[0].message
        );
    }

    #[tokio::test]
    async fn test_typo_column_has_suggestion() {
        let schema = load_schema("postgres_app.yaml");
        // The fixture has "user_id" (not in users table, which has "id") and "emial".
        // DataFusion reports the first bad column. Either way, we expect suggestions.
        let sql = load_sql("errors/typo_column.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        let error = &result.errors[0];
        assert!(
            !error.suggestions.is_empty(),
            "Typo column query should produce suggestions, got none for: {}",
            error.message
        );
    }

    #[tokio::test]
    async fn test_typo_column_emial_suggestion() {
        // Test "emial" typo directly against postgres_app schema
        let schema = load_schema("postgres_app.yaml");
        let result = validate("SELECT emial FROM users", &schema).await;
        assert!(!result.valid);
        assert!(!result.errors.is_empty());
        let error = &result.errors[0];
        assert_eq!(error.code, ErrorCode::E002);
        assert!(
            !error.suggestions.is_empty(),
            "Typo 'emial' should produce suggestions"
        );
        let has_email = error.suggestions.iter().any(|s| match &s.kind {
            SuggestionKind::DidYouMean { name } => name.contains("email"),
            _ => false,
        });
        assert!(
            has_email,
            "Should suggest 'email' for 'emial', got: {:?}",
            error.suggestions
        );
    }

    #[tokio::test]
    async fn test_wrong_join_is_invalid() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/wrong_join.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.valid, "wrong_join should be invalid");
    }

    #[tokio::test]
    async fn test_wrong_join_has_column_error() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/wrong_join.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        // "author_id" does not exist in posts — should be E002
        assert_eq!(
            result.errors[0].code,
            ErrorCode::E002,
            "Wrong join column should produce E002, got {:?}: {}",
            result.errors[0].code,
            result.errors[0].message
        );
    }

    #[tokio::test]
    async fn test_ambiguous_column_is_invalid() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/ambiguous_column.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.valid, "ambiguous_column should be invalid");
    }

    #[tokio::test]
    async fn test_ambiguous_column_has_error() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/ambiguous_column.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.errors.is_empty());
        // "id" and "user_id" are ambiguous between posts and comments
        let code = result.errors[0].code;
        assert!(
            code == ErrorCode::E004 || code == ErrorCode::E002,
            "Ambiguous column should produce E004 or E002, got {:?}: {}",
            code,
            result.errors[0].message
        );
    }

    #[tokio::test]
    async fn test_multi_error_is_invalid() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/multi_error.sql");
        let result = validate(&sql, &schema).await;
        assert!(!result.valid, "multi_error should be invalid");
    }

    #[tokio::test]
    async fn test_multi_error_has_errors() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/multi_error.sql");
        let result = validate(&sql, &schema).await;
        assert!(
            !result.errors.is_empty(),
            "multi_error should produce at least one error"
        );
    }

    #[tokio::test]
    async fn test_fix_typo_table() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = fix(&sql, &schema, &FixConfig::default()).await;
        assert!(result.fixed, "Fixer should fix typo_table");
        // Verify the fixed SQL is valid
        let revalidation = validate(&result.fixed_sql, &schema).await;
        assert!(
            revalidation.valid,
            "Fixed SQL should be valid: {}, errors: {:?}",
            result.fixed_sql, revalidation.errors
        );
    }

    #[tokio::test]
    async fn test_fix_typo_column() {
        let schema = load_schema("postgres_app.yaml");
        // The fixture has two bad columns (user_id and emial), so fixer may or may not
        // fully fix it. We just verify the fixer runs without panic and attempts fixes.
        let sql = load_sql("errors/typo_column.sql");
        let result = fix(&sql, &schema, &FixConfig::default()).await;
        assert!(
            result.iterations >= 1,
            "Fixer should attempt at least one iteration"
        );
    }

    #[tokio::test]
    async fn test_fix_single_typo_column() {
        // Test fixing a single column typo directly
        let schema = load_schema("postgres_app.yaml");
        let result = fix("SELECT emial FROM users", &schema, &FixConfig::default()).await;
        assert!(result.fixed, "Fixer should fix single column typo 'emial'");
        let revalidation = validate(&result.fixed_sql, &schema).await;
        assert!(
            revalidation.valid,
            "Fixed SQL should be valid: {}, errors: {:?}",
            result.fixed_sql, revalidation.errors
        );
    }

    #[tokio::test]
    async fn test_fix_multi_error_attempts_fixes() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/multi_error.sql");
        let result = fix(&sql, &schema, &FixConfig::default()).await;
        // Multi-error has table and column typos — fixer should try to fix at least the table
        assert!(
            result.iterations >= 1,
            "Fixer should attempt at least one iteration"
        );
    }
}

// ─── Cross-Dialect Tests ─────────────────────────────────────────────

mod cross_dialect_tests {
    use super::*;

    #[tokio::test]
    async fn test_simple_select_validates_against_snowflake() {
        let schema = load_schema("snowflake_analytics.yaml");
        let result = validate("SELECT user_id, username FROM dim_users", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against snowflake: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_simple_select_validates_against_postgres() {
        let schema = load_schema("postgres_app.yaml");
        let result = validate("SELECT id, email FROM users", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against postgres: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_simple_select_validates_against_bigquery() {
        let schema = load_schema("bigquery_marketing.yaml");
        let result = validate("SELECT customer_id, email FROM customer_profiles", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against bigquery: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_simple_select_validates_against_redshift() {
        let schema = load_schema("redshift_warehouse.yaml");
        let result = validate("SELECT product_id, product_name FROM dim_products", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against redshift: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_simple_select_validates_against_databricks() {
        let schema = load_schema("databricks_lakehouse.yaml");
        let result = validate("SELECT session_id, user_id FROM silver_sessions", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against databricks: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_simple_select_validates_against_dbt() {
        let schema = load_schema("dbt_ecommerce.yaml");
        let result = validate("SELECT order_id, customer_id FROM stg_orders", &schema).await;
        assert!(
            result.valid,
            "Simple SELECT should work against dbt: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_where_clause_across_dialects() {
        // Snowflake
        let schema = load_schema("snowflake_analytics.yaml");
        let result = validate(
            "SELECT user_id FROM dim_users WHERE is_active = true",
            &schema,
        )
        .await;
        assert!(
            result.valid,
            "WHERE clause should work against snowflake: {:?}",
            result.errors
        );

        // Postgres
        let schema = load_schema("postgres_app.yaml");
        let result = validate("SELECT id FROM users WHERE is_verified = true", &schema).await;
        assert!(
            result.valid,
            "WHERE clause should work against postgres: {:?}",
            result.errors
        );

        // BigQuery
        let schema = load_schema("bigquery_marketing.yaml");
        let result = validate(
            "SELECT customer_id FROM customer_profiles WHERE ltv > 100",
            &schema,
        )
        .await;
        assert!(
            result.valid,
            "WHERE clause should work against bigquery: {:?}",
            result.errors
        );
    }

    #[tokio::test]
    async fn test_json_output_parseable_snowflake() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("snowflake/window_functions.sql");
        let result = validate(&sql, &schema).await;
        let json_str = serde_json::to_string(&result).expect("Should serialize");
        let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("Should parse");
        assert_eq!(parsed["valid"], true);
    }

    #[tokio::test]
    async fn test_json_output_parseable_dbt() {
        let schema = load_schema("dbt_ecommerce.yaml");
        let sql = load_sql("dbt/complex_dbt_model.sql");
        let result = validate(&sql, &schema).await;
        let json_str = serde_json::to_string(&result).expect("Should serialize");
        let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("Should parse");
        assert_eq!(parsed["valid"], true);
    }
}

// ─── Snapshot Tests ──────────────────────────────────────────────────

mod snapshot_tests {
    use super::*;

    #[tokio::test]
    async fn snapshot_snowflake_cte_explain() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("snowflake/cte_with_aggregation.sql");
        let result = explain(&sql, &schema).await;
        let snapshot = serde_json::json!({
            "valid": result.valid,
            "has_explanation": result.explanation.is_some(),
            "table_count": result.explanation.as_ref().map(|e| e.lineage.tables.len()),
            "has_aggregation": result.explanation.as_ref().map(|e| e.cost_signals.has_aggregation),
            "join_count": result.explanation.as_ref().map(|e| e.cost_signals.join_count),
        });
        insta::assert_yaml_snapshot!("dialect_snowflake_cte_explain", snapshot);
    }

    #[tokio::test]
    async fn snapshot_dbt_complex_model_explain() {
        let schema = load_schema("dbt_ecommerce.yaml");
        let sql = load_sql("dbt/complex_dbt_model.sql");
        let result = explain(&sql, &schema).await;
        let snapshot = serde_json::json!({
            "valid": result.valid,
            "has_explanation": result.explanation.is_some(),
            "step_count": result.explanation.as_ref().map(|e| e.plan_steps.len()),
            "has_window_function": result.explanation.as_ref().map(|e| e.cost_signals.has_window_function),
            "complexity": result.explanation.as_ref().map(|e| format!("{}", e.cost_signals.complexity)),
        });
        insta::assert_yaml_snapshot!("dialect_dbt_complex_model_explain", snapshot);
    }

    #[tokio::test]
    async fn snapshot_typo_table_error() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = validate(&sql, &schema).await;
        let snapshot = serde_json::json!({
            "valid": result.valid,
            "error_count": result.errors.len(),
            "first_error_code": result.errors.first().map(|e| e.code.to_string()),
            "has_suggestions": result.errors.first().map(|e| !e.suggestions.is_empty()),
        });
        insta::assert_yaml_snapshot!("dialect_typo_table_error", snapshot);
    }

    #[tokio::test]
    async fn snapshot_typo_column_error() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("errors/typo_column.sql");
        let result = validate(&sql, &schema).await;
        let snapshot = serde_json::json!({
            "valid": result.valid,
            "error_count": result.errors.len(),
            "first_error_code": result.errors.first().map(|e| e.code.to_string()),
            "has_suggestions": result.errors.first().map(|e| !e.suggestions.is_empty()),
        });
        insta::assert_yaml_snapshot!("dialect_typo_column_error", snapshot);
    }
}

// ─── Additional Coverage Tests ───────────────────────────────────────

mod additional_tests {
    use super::*;

    #[tokio::test]
    async fn test_snowflake_having_clause() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("snowflake/multi_join_analytics.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        // The multi_join_analytics query uses HAVING
        assert!(
            explanation.cost_signals.has_aggregation,
            "Query with HAVING should have aggregation"
        );
    }

    #[tokio::test]
    async fn test_redshift_star_schema_aggregation() {
        let schema = load_schema("redshift_warehouse.yaml");
        let sql = load_sql("redshift/star_schema_query.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        let explanation = result.explanation.expect("should have explanation");
        assert!(
            explanation.cost_signals.has_aggregation,
            "Star schema query uses GROUP BY, should detect aggregation"
        );
    }

    #[tokio::test]
    async fn test_dbt_schema_table_count() {
        let schema = load_schema("dbt_ecommerce.yaml");
        let table_names = schema.table_names();
        // dbt schema has 8 tables: stg_orders, stg_customers, stg_payments,
        // stg_products, stg_order_items, int_order_payments, customers, orders
        assert_eq!(
            table_names.len(),
            8,
            "dbt_ecommerce should have 8 tables, got {} ({:?})",
            table_names.len(),
            table_names
        );
    }

    #[tokio::test]
    async fn test_snowflake_schema_table_count() {
        let schema = load_schema("snowflake_analytics.yaml");
        let table_names = schema.table_names();
        assert_eq!(
            table_names.len(),
            5,
            "snowflake_analytics should have 5 tables, got {} ({:?})",
            table_names.len(),
            table_names
        );
    }

    #[tokio::test]
    async fn test_bigquery_explain_metadata_dialect() {
        let schema = load_schema("bigquery_marketing.yaml");
        let sql = load_sql("bigquery/customer_ltv.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        assert_eq!(
            result.metadata.dialect, "bigquery",
            "BigQuery schema should report bigquery dialect"
        );
    }

    #[tokio::test]
    async fn test_snowflake_explain_metadata_dialect() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("snowflake/window_functions.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        assert_eq!(
            result.metadata.dialect, "snowflake",
            "Snowflake schema should report snowflake dialect"
        );
    }

    #[tokio::test]
    async fn test_postgres_explain_metadata_dialect() {
        let schema = load_schema("postgres_app.yaml");
        let sql = load_sql("postgres/complex_analytics.sql");
        let result = explain(&sql, &schema).await;
        assert!(result.valid);
        assert_eq!(
            result.metadata.dialect, "postgres",
            "Postgres schema should report postgres dialect"
        );
    }

    #[tokio::test]
    async fn test_error_result_serializes_to_json() {
        let schema = load_schema("snowflake_analytics.yaml");
        let sql = load_sql("errors/typo_table.sql");
        let result = validate(&sql, &schema).await;
        let json_str = serde_json::to_string(&result).expect("Should serialize");
        let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("Should parse");
        assert_eq!(parsed["valid"], false);
        assert!(!parsed["errors"].as_array().unwrap().is_empty());
    }

    #[tokio::test]
    async fn test_explain_result_serializes_to_json() {
        let schema = load_schema("dbt_ecommerce.yaml");
        let sql = load_sql("dbt/mart_customers.sql");
        let result = explain(&sql, &schema).await;
        let json_str = serde_json::to_string(&result).expect("Should serialize");
        let parsed: serde_json::Value = serde_json::from_str(&json_str).expect("Should parse");
        assert_eq!(parsed["valid"], true);
        assert!(parsed["explanation"].is_object());
        assert!(parsed["metadata"]["plan_node_count"].as_u64().unwrap() > 0);
    }
}
