//! Comprehensive integration tests for altimate-core.
//!
//! Tests cover: valid queries, table errors, column errors, ambiguous columns,
//! type mismatches, syntax errors, fuzzy matching, schema loading, output formatting,
//! and edge cases.

use altimate_core::errors::types::{ErrorCode, ErrorKind, SuggestionKind, ValidationResult};
use altimate_core::output::{json, text};
use altimate_core::schema::types::SchemaDefinition;
use altimate_core::validate;
use std::path::Path;

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

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

fn load_fixture(path: &str) -> String {
    std::fs::read_to_string(
        Path::new(env!("CARGO_MANIFEST_DIR"))
            .join("../../")
            .join(path),
    )
    .unwrap_or_else(|_| panic!("Failed to load fixture: {}", path))
}

// ─── Valid Query Tests ────────────────────────────────────────────────

#[tokio::test]
async fn valid_simple_select() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/simple_select.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
    assert!(result.errors.is_empty());
}

#[tokio::test]
async fn valid_select_star() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/select_star.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_where_clause() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/where_clause.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_join() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/join.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_aggregation() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/aggregation.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_subquery() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/subquery.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_cte() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/valid/cte.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_complex_multi_join() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/complex/multi_join.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_complex_window_function() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/complex/window_function.sql");
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn valid_select_with_alias() {
    let schema = ecommerce_schema();
    let result = validate(
        "SELECT id AS user_id, email AS user_email FROM users",
        &schema,
    )
    .await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

// ─── Table Not Found Tests ───────────────────────────────────────────

#[tokio::test]
async fn table_not_found_nonexistent() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/invalid_table/nonexistent.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(
        error.code,
        ErrorCode::E001,
        "Expected E001 for table not found"
    );
    assert!(
        matches!(&error.kind, ErrorKind::TableNotFound { .. }),
        "Expected TableNotFound kind, got {:?}",
        error.kind
    );
}

#[tokio::test]
async fn table_not_found_typo_singular() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/invalid_table/typo_singular.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E001);
    // "user" should suggest "users"
    if let ErrorKind::TableNotFound { table } = &error.kind {
        assert_eq!(table, "user", "Expected extracted table name to be 'user'");
    }
}

#[tokio::test]
async fn table_not_found_typo_suggests_users() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM user", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert!(
        !error.suggestions.is_empty(),
        "Expected suggestions for typo 'user'"
    );
    let suggestion = &error.suggestions[0];
    match &suggestion.kind {
        SuggestionKind::DidYouMean { name } => {
            assert!(
                name.contains("users"),
                "Expected suggestion to contain 'users', got '{}'",
                name
            );
        }
        _ => panic!("Expected DidYouMean suggestion kind"),
    }
}

// ─── Column Not Found Tests ─────────────────────────────────────────

#[tokio::test]
async fn column_not_found_nonexistent() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/invalid_column/nonexistent.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(
        error.code,
        ErrorCode::E002,
        "Expected E002 for column not found"
    );
    assert!(
        matches!(&error.kind, ErrorKind::ColumnNotFound { .. }),
        "Expected ColumnNotFound kind, got {:?}",
        error.kind
    );
}

#[tokio::test]
async fn column_not_found_typo_created_date() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/invalid_column/typo_created_date.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E002);
    // "created_date" should suggest "created_at"
    if !error.suggestions.is_empty() {
        let suggestion = &error.suggestions[0];
        match &suggestion.kind {
            SuggestionKind::DidYouMean { name } => {
                assert!(
                    name.contains("created_at"),
                    "Expected suggestion to contain 'created_at', got '{}'",
                    name
                );
            }
            _ => panic!("Expected DidYouMean suggestion kind"),
        }
    }
}

#[tokio::test]
async fn column_not_found_typo_total() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/invalid_column/typo_total.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E002);
    // "total" should suggest "total_amount"
    if !error.suggestions.is_empty() {
        let suggestion = &error.suggestions[0];
        match &suggestion.kind {
            SuggestionKind::DidYouMean { name } => {
                assert!(
                    name.contains("total_amount"),
                    "Expected suggestion to contain 'total_amount', got '{}'",
                    name
                );
            }
            _ => panic!("Expected DidYouMean suggestion kind"),
        }
    }
}

#[tokio::test]
async fn column_not_found_completely_wrong() {
    let schema = ecommerce_schema();
    let result = validate("SELECT xyz_nonexistent FROM users", &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
}

// ─── Ambiguous Column Tests ─────────────────────────────────────────

#[tokio::test]
async fn ambiguous_column_unqualified_id() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/ambiguous_column/unqualified_id.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(
        error.code,
        ErrorCode::E004,
        "Expected E004 for ambiguous column"
    );
    assert!(
        matches!(&error.kind, ErrorKind::AmbiguousColumn { .. }),
        "Expected AmbiguousColumn kind, got {:?}",
        error.kind
    );
}

#[tokio::test]
async fn ambiguous_column_unqualified_status() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/ambiguous_column/unqualified_status.sql");
    let result = validate(&sql, &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(
        error.code,
        ErrorCode::E004,
        "Expected E004 for ambiguous column"
    );
}

// ─── Type Mismatch Tests ────────────────────────────────────────────

#[tokio::test]
async fn type_mismatch_string_vs_int() {
    let schema = ecommerce_schema();
    let sql = load_fixture("tests/fixtures/queries/type_mismatch/string_vs_int.sql");
    let result = validate(&sql, &schema).await;
    // DataFusion may or may not catch this as a type error vs treating it as a
    // string comparison. If it does catch it, verify it's flagged.
    // If DataFusion allows it (implicit cast), verify it's valid.
    // Either outcome is acceptable — this tests that we don't panic.
    assert!(
        result.valid || !result.errors.is_empty(),
        "Expected either valid or at least one error"
    );
}

#[tokio::test]
async fn type_mismatch_string_arithmetic() {
    let schema = ecommerce_schema();
    // Comparing string column with arithmetic should fail
    let result = validate("SELECT * FROM users WHERE email + 1 > 5", &schema).await;
    // This may fail or be coerced — the key thing is we don't panic
    let _ = result;
}

// ─── Syntax Error Tests ─────────────────────────────────────────────

#[tokio::test]
async fn syntax_error_malformed_sql() {
    let schema = ecommerce_schema();
    let result = validate("SELECTZ * FROM users", &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(
        error.code,
        ErrorCode::E000,
        "Expected E000 for syntax error"
    );
    assert!(
        matches!(&error.kind, ErrorKind::SyntaxError),
        "Expected SyntaxError kind, got {:?}",
        error.kind
    );
}

#[tokio::test]
async fn syntax_error_missing_from() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * WHERE id = 1", &schema).await;
    // sqlparser may handle this differently (it might parse it or fail)
    // but we should not panic
    let _ = result;
}

#[tokio::test]
async fn syntax_error_unterminated_string() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users WHERE name = 'unterminated", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E000);
}

#[tokio::test]
async fn syntax_error_double_comma() {
    let schema = ecommerce_schema();
    let result = validate("SELECT id,, email FROM users", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E000);
}

// ─── Edge Case Tests ────────────────────────────────────────────────

#[tokio::test]
async fn edge_case_empty_sql() {
    let schema = ecommerce_schema();
    let result = validate("", &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
    let error = &result.errors[0];
    assert_eq!(error.code, ErrorCode::E000);
    assert!(error.message.contains("Empty"));
}

#[tokio::test]
async fn edge_case_whitespace_only() {
    let schema = ecommerce_schema();
    let result = validate("   \n\t  ", &schema).await;
    assert!(!result.valid);
}

#[tokio::test]
async fn edge_case_sql_comment_only() {
    let schema = ecommerce_schema();
    let result = validate("-- this is a comment", &schema).await;
    assert!(!result.valid);
}

#[tokio::test]
async fn edge_case_block_comment_only() {
    let schema = ecommerce_schema();
    let result = validate("/* block comment */", &schema).await;
    assert!(!result.valid);
}

#[tokio::test]
async fn edge_case_semicolons_only() {
    let schema = ecommerce_schema();
    let result = validate(";;;", &schema).await;
    assert!(!result.valid);
}

#[tokio::test]
async fn edge_case_very_long_sql() {
    let schema = ecommerce_schema();
    // Build a SQL query with many OR conditions
    let mut sql = "SELECT * FROM users WHERE ".to_string();
    for i in 0..100 {
        if i > 0 {
            sql.push_str(" OR ");
        }
        sql.push_str(&format!("id = {}", i));
    }
    let result = validate(&sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid for long query, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn edge_case_sql_with_trailing_semicolon() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users;", &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn edge_case_sql_with_leading_whitespace() {
    let schema = ecommerce_schema();
    let result = validate("  \n  SELECT * FROM users", &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn edge_case_case_insensitive_keywords() {
    let schema = ecommerce_schema();
    let result = validate("select id, email from users where age > 25", &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

// ─── Fuzzy Matching Tests ───────────────────────────────────────────

#[tokio::test]
async fn fuzzy_table_typo_order_suggests_orders() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM order", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert!(
        !error.suggestions.is_empty(),
        "Expected suggestions for 'order'"
    );
    let has_orders = error.suggestions.iter().any(|s| match &s.kind {
        SuggestionKind::DidYouMean { name } => name.contains("orders"),
        _ => false,
    });
    assert!(has_orders, "Expected suggestion containing 'orders'");
}

#[tokio::test]
async fn fuzzy_table_typo_product_suggests_products() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM product", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    let has_products = error.suggestions.iter().any(|s| match &s.kind {
        SuggestionKind::DidYouMean { name } => name.contains("products"),
        _ => false,
    });
    assert!(has_products, "Expected suggestion containing 'products'");
}

#[tokio::test]
async fn fuzzy_no_suggestions_for_gibberish_table() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM zzzzzzz", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    assert!(
        error.suggestions.is_empty(),
        "Expected no suggestions for gibberish table name, got {:?}",
        error.suggestions
    );
}

#[tokio::test]
async fn fuzzy_suggestion_confidence_is_valid() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM user", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    for suggestion in &error.suggestions {
        assert!(
            suggestion.confidence >= 0.0 && suggestion.confidence <= 1.0,
            "Confidence {} out of range [0, 1]",
            suggestion.confidence
        );
        assert!(
            suggestion.confidence >= 0.7,
            "Suggestion confidence {} below threshold 0.7",
            suggestion.confidence
        );
    }
}

#[tokio::test]
async fn fuzzy_suggestion_message_format() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM user", &schema).await;
    assert!(!result.valid);
    let error = &result.errors[0];
    for suggestion in &error.suggestions {
        assert!(
            suggestion.message.starts_with("Did you mean"),
            "Suggestion message should start with 'Did you mean', got: {}",
            suggestion.message
        );
    }
}

// ─── Schema Loading Tests ───────────────────────────────────────────

#[test]
fn schema_load_from_yaml_string() {
    let yaml = r#"
version: "1"
dialect: generic
tables:
  test_table:
    columns:
      - name: id
        type: INT
        nullable: false
      - name: name
        type: VARCHAR
"#;
    let schema = SchemaDefinition::from_yaml(yaml).unwrap();
    assert_eq!(schema.tables.len(), 1);
    assert!(schema.tables.contains_key("test_table"));
    assert_eq!(schema.tables["test_table"].columns.len(), 2);
}

#[test]
fn schema_load_from_json_string() {
    let json = r#"{
  "version": "1",
  "tables": {
    "items": {
      "columns": [
        {"name": "id", "type": "INT", "nullable": false},
        {"name": "label", "type": "VARCHAR"}
      ]
    }
  }
}"#;
    let schema = SchemaDefinition::from_json(json).unwrap();
    assert_eq!(schema.tables.len(), 1);
    assert!(schema.tables.contains_key("items"));
}

#[test]
fn schema_load_from_yaml_file() {
    let path =
        Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/fixtures/schemas/ecommerce.yaml");
    let schema = SchemaDefinition::from_file(&path).unwrap();
    assert_eq!(schema.tables.len(), 4);
    assert!(schema.tables.contains_key("users"));
    assert!(schema.tables.contains_key("orders"));
    assert!(schema.tables.contains_key("products"));
    assert!(schema.tables.contains_key("order_items"));
}

#[test]
fn schema_load_ecommerce_has_correct_columns() {
    let schema = ecommerce_schema();
    let users = &schema.tables["users"];
    assert_eq!(users.columns.len(), 7);
    let col_names: Vec<&str> = users.columns.iter().map(|c| c.name.as_str()).collect();
    assert!(col_names.contains(&"id"));
    assert!(col_names.contains(&"email"));
    assert!(col_names.contains(&"name"));
    assert!(col_names.contains(&"age"));
    assert!(col_names.contains(&"status"));
    assert!(col_names.contains(&"created_at"));
    assert!(col_names.contains(&"updated_at"));
}

#[test]
fn schema_empty_tables_rejected() {
    let yaml = r#"
version: "1"
tables: {}
"#;
    let result = SchemaDefinition::from_yaml(yaml);
    assert!(result.is_err());
}

#[test]
fn schema_empty_columns_rejected() {
    let yaml = r#"
version: "1"
tables:
  empty_table:
    columns: []
"#;
    let result = SchemaDefinition::from_yaml(yaml);
    assert!(result.is_err());
}

#[test]
fn schema_invalid_pk_column_rejected() {
    let yaml = r#"
version: "1"
tables:
  test_table:
    columns:
      - name: id
        type: INT
    primary_key: [nonexistent]
"#;
    let result = SchemaDefinition::from_yaml(yaml);
    assert!(result.is_err());
}

#[test]
fn schema_invalid_fk_reference_rejected() {
    let yaml = r#"
version: "1"
tables:
  test_table:
    columns:
      - name: id
        type: INT
      - name: ref_id
        type: INT
    foreign_keys:
      - columns: [ref_id]
        references:
          table: nonexistent_table
          columns: [id]
"#;
    let result = SchemaDefinition::from_yaml(yaml);
    assert!(result.is_err());
}

#[test]
fn schema_table_names_helper() {
    let schema = ecommerce_schema();
    let names = schema.table_names();
    assert_eq!(names.len(), 4);
    assert!(names.contains(&"users"));
    assert!(names.contains(&"orders"));
    assert!(names.contains(&"products"));
    assert!(names.contains(&"order_items"));
}

#[test]
fn schema_column_names_helper() {
    let schema = ecommerce_schema();
    let user_cols = schema.column_names("users").unwrap();
    assert!(user_cols.contains(&"id"));
    assert!(user_cols.contains(&"email"));
    assert!(schema.column_names("nonexistent").is_none());
}

#[test]
fn schema_all_column_names_helper() {
    let schema = ecommerce_schema();
    let all = schema.all_column_names();
    // 7 users + 6 orders + 6 products + 5 order_items = 24 columns total
    assert_eq!(all.len(), 24, "Expected 24 total columns across all tables");
}

#[test]
fn schema_dialect_default_is_generic() {
    let yaml = r#"
version: "1"
tables:
  t:
    columns:
      - name: id
        type: INT
"#;
    let schema = SchemaDefinition::from_yaml(yaml).unwrap();
    assert_eq!(
        schema.dialect,
        altimate_core::schema::types::SqlDialect::Generic
    );
}

#[test]
fn schema_unsupported_file_format() {
    let result = SchemaDefinition::from_file(Path::new("schema.txt"));
    assert!(result.is_err());
}

// ─── Output Format Tests ────────────────────────────────────────────

#[tokio::test]
async fn output_json_valid_query() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users", &schema).await;
    let json_str = json::format_json(&result);
    // Verify it's valid JSON
    let parsed: serde_json::Value =
        serde_json::from_str(&json_str).expect("JSON output should be valid JSON");
    assert_eq!(parsed["valid"], true);
    assert!(parsed["errors"].as_array().unwrap().is_empty());
}

#[tokio::test]
async fn output_json_invalid_query() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM nonexistent", &schema).await;
    let json_str = json::format_json(&result);
    let parsed: serde_json::Value =
        serde_json::from_str(&json_str).expect("JSON output should be valid JSON");
    assert_eq!(parsed["valid"], false);
    assert!(!parsed["errors"].as_array().unwrap().is_empty());
}

#[tokio::test]
async fn output_json_compact_is_single_line() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users", &schema).await;
    let compact = json::format_json_compact(&result);
    assert!(
        !compact.contains('\n'),
        "Compact JSON should be single line"
    );
    let _: serde_json::Value =
        serde_json::from_str(&compact).expect("Compact JSON should be valid JSON");
}

#[tokio::test]
async fn output_json_contains_error_code() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM nonexistent", &schema).await;
    let json_str = json::format_json(&result);
    let parsed: serde_json::Value = serde_json::from_str(&json_str).unwrap();
    let errors = parsed["errors"].as_array().unwrap();
    assert!(!errors.is_empty());
    let error = &errors[0];
    assert!(error["code"].is_string(), "Error should have a code field");
    assert!(
        error["message"].is_string(),
        "Error should have a message field"
    );
}

#[tokio::test]
async fn output_text_valid_query_shows_checkmark() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users", &schema).await;
    let text_output = text::format_text(&result);
    assert!(
        text_output.contains('✓'),
        "Valid query text should contain checkmark"
    );
    assert!(
        text_output.contains("valid"),
        "Valid query text should mention 'valid'"
    );
}

#[tokio::test]
async fn output_text_invalid_query_shows_cross() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM nonexistent", &schema).await;
    let text_output = text::format_text(&result);
    assert!(
        text_output.contains('✗'),
        "Invalid query text should contain cross mark"
    );
    assert!(
        text_output.contains("Error"),
        "Invalid query text should mention 'Error'"
    );
}

#[tokio::test]
async fn output_text_shows_error_count() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM nonexistent", &schema).await;
    let text_output = text::format_text(&result);
    assert!(
        text_output.contains("1 error"),
        "Text output should show error count, got: {}",
        text_output
    );
}

#[tokio::test]
async fn output_text_shows_suggestions() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM user", &schema).await;
    let text_output = text::format_text(&result);
    if !result.errors[0].suggestions.is_empty() {
        assert!(
            text_output.contains("Did you mean"),
            "Text output should show suggestions, got: {}",
            text_output
        );
    }
}

// ─── ValidationResult API Tests ─────────────────────────────────────

#[test]
fn validation_result_ok_is_valid() {
    let result = ValidationResult::ok(Default::default());
    assert!(result.valid);
    assert!(result.errors.is_empty());
    assert!(result.warnings.is_empty());
}

#[test]
fn validation_result_syntax_error_is_invalid() {
    let result = ValidationResult::syntax_error("test error".to_string(), None);
    assert!(!result.valid);
    assert_eq!(result.errors.len(), 1);
    assert_eq!(result.errors[0].code, ErrorCode::E000);
}

#[test]
fn validation_result_serializes_to_json() {
    let result = ValidationResult::ok(Default::default());
    let json = serde_json::to_string(&result).unwrap();
    assert!(json.contains("\"valid\":true"));
}

#[test]
fn validation_result_deserializes_from_json() {
    let json = r#"{"valid":true,"errors":[],"warnings":[],"metadata":{"tables_referenced":[],"join_count":0,"has_aggregation":false,"has_subquery":false,"complexity":"low"}}"#;
    let result: ValidationResult = serde_json::from_str(json).unwrap();
    assert!(result.valid);
}

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

#[tokio::test]
async fn snapshot_valid_result() {
    let schema = ecommerce_schema();
    let result = validate("SELECT id, email FROM users", &schema).await;
    insta::assert_yaml_snapshot!("valid_simple_select", result);
}

#[tokio::test]
async fn snapshot_table_not_found_result() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM nonexistent_table", &schema).await;
    // Only snapshot the error structure (suggestions may vary by strsim version)
    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()),
    });
    insta::assert_yaml_snapshot!("table_not_found", snapshot);
}

#[tokio::test]
async fn snapshot_syntax_error_result() {
    let schema = ecommerce_schema();
    let result = validate("SELECTZ * FROM users", &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()),
    });
    insta::assert_yaml_snapshot!("syntax_error", snapshot);
}

// ─── Multiple Statement Tests ───────────────────────────────────────

#[tokio::test]
async fn multiple_statements_both_valid() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users; SELECT * FROM orders", &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn multiple_statements_second_invalid() {
    let schema = ecommerce_schema();
    let result = validate("SELECT * FROM users; SELECT * FROM nonexistent", &schema).await;
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
}

// ─── Cross-table Query Tests ────────────────────────────────────────

#[tokio::test]
async fn cross_table_join_all_four_tables() {
    let schema = ecommerce_schema();
    let sql = r#"
        SELECT u.name, o.total_amount, p.name AS product, oi.quantity
        FROM users u
        JOIN orders o ON u.id = o.user_id
        JOIN order_items oi ON o.id = oi.order_id
        JOIN products p ON oi.product_id = p.id
    "#;
    let result = validate(sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn cross_table_subquery_with_in() {
    let schema = ecommerce_schema();
    let sql = r#"
        SELECT name FROM users
        WHERE id IN (
            SELECT user_id FROM orders
            WHERE total_amount > 100
        )
    "#;
    let result = validate(sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

#[tokio::test]
async fn cross_table_correlated_subquery() {
    let schema = ecommerce_schema();
    let sql = r#"
        SELECT u.name, (
            SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id
        ) AS order_count
        FROM users u
    "#;
    let result = validate(sql, &schema).await;
    assert!(
        result.valid,
        "Expected valid, got errors: {:?}",
        result.errors
    );
}

// ─── Error Code Consistency Tests ───────────────────────────────────

#[test]
fn error_code_display() {
    assert_eq!(format!("{}", ErrorCode::E000), "E000");
    assert_eq!(format!("{}", ErrorCode::E001), "E001");
    assert_eq!(format!("{}", ErrorCode::E002), "E002");
    assert_eq!(format!("{}", ErrorCode::E003), "E003");
    assert_eq!(format!("{}", ErrorCode::E004), "E004");
    assert_eq!(format!("{}", ErrorCode::E005), "E005");
    assert_eq!(format!("{}", ErrorCode::E006), "E006");
    assert_eq!(format!("{}", ErrorCode::E099), "E099");
}

#[test]
fn error_code_serialization_roundtrip() {
    for code in &[
        ErrorCode::E000,
        ErrorCode::E001,
        ErrorCode::E002,
        ErrorCode::E003,
        ErrorCode::E004,
        ErrorCode::E099,
    ] {
        let json = serde_json::to_string(code).unwrap();
        let deserialized: ErrorCode = serde_json::from_str(&json).unwrap();
        assert_eq!(*code, deserialized);
    }
}
