//! Dialect-specific validation tests.
//!
//! Tests cover Snowflake, BigQuery, Databricks, and dbt Jinja patterns across
//! validation, safety scanning, PII masking, transpilation, and pre-validation.

use altimate_core::pii::suggest_masking;
use altimate_core::polyglot::extractor::extract_tables;
use altimate_core::polyglot::formatter::format_sql;
use altimate_core::polyglot::prevalidator::prevalidate_syntax;
use altimate_core::polyglot::transpiler::transpile;
use altimate_core::safety::{SafetyConfig, SafetyRule, is_safe, scan_sql};
use altimate_core::schema::types::{ColumnDef, SchemaDefinition, SqlDialect};
use altimate_core::validator::engine::validate;

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

fn schema_with_dialect(dialect: SqlDialect) -> SchemaDefinition {
    let mut schema = ecommerce_schema();
    schema.dialect = dialect;
    schema
}

fn make_col(name: &str, data_type: &str) -> ColumnDef {
    ColumnDef {
        name: name.to_string(),
        data_type: data_type.to_string(),
        nullable: true,
        description: None,
        tags: vec![],
        synonyms: vec![],

        statistics: None,
    }
}

// ═══════════════════════════════════════════════════════════════════════
// Snowflake — validation
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn dialect_snowflake_basic_select() {
    let schema = schema_with_dialect(SqlDialect::Snowflake);
    let result = validate("SELECT id, email FROM users", &schema).await;
    assert!(result.valid);
}

#[tokio::test]
async fn dialect_snowflake_ilike() {
    let schema = schema_with_dialect(SqlDialect::Snowflake);
    let result = validate("SELECT * FROM users WHERE name ILIKE '%smith%'", &schema).await;
    assert!(result.valid);
}

#[tokio::test]
async fn dialect_snowflake_qualify_prevalidation() {
    let result = prevalidate_syntax(
        "SELECT id, ROW_NUMBER() OVER (ORDER BY id) AS rn FROM users QUALIFY rn = 1",
        SqlDialect::Snowflake,
    );
    // Snowflake's QUALIFY is a valid syntax
    assert!(result.valid || !result.errors.is_empty());
}

#[tokio::test]
async fn dialect_snowflake_flatten() {
    let result = prevalidate_syntax(
        "SELECT f.value FROM users, LATERAL FLATTEN(input => users.tags) f",
        SqlDialect::Snowflake,
    );
    // FLATTEN is Snowflake-specific
    assert!(result.valid || !result.errors.is_empty());
}

#[test]
fn dialect_snowflake_system_function_blocked() {
    assert!(!is_safe("SELECT SYSTEM$TYPEOF(current_version())"));
}

#[test]
fn dialect_snowflake_copy_into_blocked() {
    assert!(!is_safe("COPY INTO @my_stage FROM users"));
}

#[test]
fn dialect_snowflake_create_stage_blocked() {
    assert!(!is_safe("CREATE STAGE my_stage"));
}

#[test]
fn dialect_snowflake_masking_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn dialect_snowflake_masking_ssn() {
    let col = make_col("ssn", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("***-**-"));
}

#[test]
fn dialect_snowflake_masking_credit_card() {
    let col = make_col("credit_card", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Snowflake).expect("should suggest");
    assert!(masking.contains("RIGHT"));
}

// ═══════════════════════════════════════════════════════════════════════
// BigQuery — validation
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn dialect_bigquery_basic_select() {
    let schema = schema_with_dialect(SqlDialect::BigQuery);
    let result = validate("SELECT id, email FROM users", &schema).await;
    assert!(result.valid);
}

#[tokio::test]
async fn dialect_bigquery_backtick_table() {
    let result = prevalidate_syntax(
        "SELECT * FROM `project.dataset.table`",
        SqlDialect::BigQuery,
    );
    // BigQuery uses backtick-quoted identifiers
    assert!(result.valid || !result.errors.is_empty());
}

#[tokio::test]
async fn dialect_bigquery_struct_type() {
    let result = prevalidate_syntax("SELECT STRUCT(1 AS a, 'hello' AS b)", SqlDialect::BigQuery);
    assert!(result.valid);
}

#[tokio::test]
async fn dialect_bigquery_array_type() {
    let result = prevalidate_syntax("SELECT ARRAY[1, 2, 3]", SqlDialect::BigQuery);
    // Array literal syntax
    assert!(result.valid || !result.errors.is_empty());
}

#[test]
fn dialect_bigquery_merge_blocked() {
    assert!(!is_safe(
        "MERGE INTO target USING source ON target.id = source.id \
         WHEN MATCHED THEN UPDATE SET name = source.name"
    ));
}

#[test]
fn dialect_bigquery_masking_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::BigQuery).expect("should suggest");
    assert!(masking.contains("SHA256"));
}

#[test]
fn dialect_bigquery_masking_dob() {
    let col = make_col("dob", "DATE");
    let masking = suggest_masking(&col, SqlDialect::BigQuery).expect("should suggest");
    assert!(masking.contains("DATE_TRUNC"));
}

// ═══════════════════════════════════════════════════════════════════════
// Databricks — validation
// ═══════════════════════════════════════════════════════════════════════

#[tokio::test]
async fn dialect_databricks_basic_select() {
    let schema = schema_with_dialect(SqlDialect::Databricks);
    let result = validate("SELECT id, email FROM users", &schema).await;
    assert!(result.valid);
}

#[test]
fn dialect_databricks_show_tables_probe() {
    let config = SafetyConfig::default();
    let result = scan_sql("SHOW TABLES", &config);
    assert!(
        result
            .threats
            .iter()
            .any(|t| t.rule == SafetyRule::InfoSchemaProbe)
    );
}

#[test]
fn dialect_databricks_show_databases_probe() {
    let config = SafetyConfig::default();
    let result = scan_sql("SHOW DATABASES", &config);
    assert!(
        result
            .threats
            .iter()
            .any(|t| t.rule == SafetyRule::InfoSchemaProbe)
    );
}

#[test]
fn dialect_databricks_describe_table_probe() {
    let config = SafetyConfig::default();
    let result = scan_sql("DESCRIBE TABLE users", &config);
    assert!(
        result
            .threats
            .iter()
            .any(|t| t.rule == SafetyRule::InfoSchemaProbe)
    );
}

#[test]
fn dialect_databricks_masking_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Databricks).expect("should suggest");
    assert!(masking.contains("sha2"));
}

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

#[test]
fn dialect_dbt_ref_expression() {
    assert!(!is_safe("SELECT * FROM {{ ref('stg_users') }}"));
}

#[test]
fn dialect_dbt_source_expression() {
    assert!(!is_safe("SELECT * FROM {{ source('raw', 'users') }}"));
}

#[test]
fn dialect_dbt_config_expression() {
    assert!(!is_safe(
        "SELECT * FROM {{ config.get('malicious_table') }}"
    ));
}

#[test]
fn dialect_dbt_jinja_statement() {
    assert!(!is_safe(
        "{% set table = 'users' %}SELECT * FROM {{ table }}"
    ));
}

#[test]
fn dialect_dbt_jinja_if_block() {
    assert!(!is_safe("{% if true %}SELECT 1{% endif %}"));
}

#[test]
fn dialect_dbt_jinja_for_loop() {
    assert!(!is_safe(
        "{% for i in range(5) %}SELECT {{ i }};{% endfor %}"
    ));
}

#[test]
fn dialect_dbt_jinja_macro_call() {
    assert!(!is_safe("SELECT * FROM {{ generate_schema_name() }}"));
}

#[test]
fn dialect_dbt_jinja_critical_severity() {
    let config = SafetyConfig::default();
    let result = scan_sql("{% set x = 1 %}SELECT {{ x }}", &config);
    let jinja_threats: Vec<_> = result
        .threats
        .iter()
        .filter(|t| t.message.contains("Jinja"))
        .collect();
    assert!(!jinja_threats.is_empty());
}

#[test]
fn dialect_dbt_jinja_expression_in_where() {
    assert!(!is_safe(
        "SELECT * FROM users WHERE status = {{ var('active_status') }}"
    ));
}

#[test]
fn dialect_dbt_jinja_nested_ref() {
    assert!(!is_safe(
        "SELECT * FROM {{ ref('stg_orders') }} o JOIN {{ ref('stg_users') }} u ON o.user_id = u.id"
    ));
}

// ═══════════════════════════════════════════════════════════════════════
// Transpilation between dialects
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_transpile_generic_to_snowflake() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::Snowflake,
    );
    assert!(result.success);
    assert!(!result.transpiled_sql.is_empty());
}

#[test]
fn dialect_transpile_generic_to_bigquery() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::BigQuery,
    );
    assert!(result.success);
    assert!(!result.transpiled_sql.is_empty());
}

#[test]
fn dialect_transpile_generic_to_postgres() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::Postgres,
    );
    assert!(result.success);
}

#[test]
fn dialect_transpile_generic_to_mysql() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::MySQL,
    );
    assert!(result.success);
}

#[test]
fn dialect_transpile_postgres_to_snowflake() {
    let result = transpile(
        "SELECT id, name FROM users WHERE id = 1",
        SqlDialect::Postgres,
        SqlDialect::Snowflake,
    );
    assert!(result.success);
}

#[test]
fn dialect_transpile_snowflake_to_bigquery() {
    let result = transpile(
        "SELECT id, name FROM users WHERE id = 1",
        SqlDialect::Snowflake,
        SqlDialect::BigQuery,
    );
    assert!(result.success);
}

#[test]
fn dialect_transpile_preserves_original() {
    let sql = "SELECT id FROM users";
    let result = transpile(sql, SqlDialect::Generic, SqlDialect::Snowflake);
    assert_eq!(result.original_sql, sql);
}

#[test]
fn dialect_transpile_has_dialect_info() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::Snowflake,
    );
    assert_eq!(result.source_dialect, SqlDialect::Generic.to_string());
    assert_eq!(result.target_dialect, SqlDialect::Snowflake.to_string());
}

// ═══════════════════════════════════════════════════════════════════════
// Pre-validation with dialects
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_prevalidate_generic_valid() {
    let result = prevalidate_syntax("SELECT id FROM users", SqlDialect::Generic);
    assert!(result.valid);
}

#[test]
fn dialect_prevalidate_generic_invalid() {
    let result = prevalidate_syntax("SELECTZ broken", SqlDialect::Generic);
    assert!(!result.valid);
    assert!(!result.errors.is_empty());
}

#[test]
fn dialect_prevalidate_postgres_valid() {
    let result = prevalidate_syntax("SELECT id FROM users WHERE id = 1", SqlDialect::Postgres);
    assert!(result.valid);
}

#[test]
fn dialect_prevalidate_snowflake_valid() {
    let result = prevalidate_syntax("SELECT id FROM users WHERE id = 1", SqlDialect::Snowflake);
    assert!(result.valid);
}

#[test]
fn dialect_prevalidate_bigquery_valid() {
    let result = prevalidate_syntax("SELECT id FROM users WHERE id = 1", SqlDialect::BigQuery);
    assert!(result.valid);
}

#[test]
fn dialect_prevalidate_mysql_valid() {
    let result = prevalidate_syntax("SELECT id FROM users WHERE id = 1", SqlDialect::MySQL);
    assert!(result.valid);
}

#[test]
fn dialect_prevalidate_error_has_message() {
    let result = prevalidate_syntax("SELECTZ broken", SqlDialect::Generic);
    assert!(!result.valid);
    assert!(!result.errors[0].message.is_empty());
}

// ═══════════════════════════════════════════════════════════════════════
// Table extraction with dialects
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_extract_tables_generic() {
    let tables = extract_tables("SELECT id FROM users", SqlDialect::Generic)
        .expect("extract_tables should succeed for Generic");
    // polyglot-sql may or may not return table names depending on AST analysis
    let _ = tables;
}

#[test]
fn dialect_extract_tables_join() {
    let tables = extract_tables(
        "SELECT u.id FROM users u JOIN orders o ON u.id = o.user_id",
        SqlDialect::Generic,
    )
    .expect("extract_tables should succeed for join query");
    let _ = tables;
}

#[test]
fn dialect_extract_tables_snowflake() {
    let tables = extract_tables("SELECT id FROM users", SqlDialect::Snowflake)
        .expect("extract_tables should succeed for Snowflake");
    let _ = tables;
}

#[test]
fn dialect_extract_tables_bigquery() {
    let tables = extract_tables("SELECT id FROM users", SqlDialect::BigQuery)
        .expect("extract_tables should succeed for BigQuery");
    let _ = tables;
}

// ═══════════════════════════════════════════════════════════════════════
// SQL formatting with dialects
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_format_sql_generic() {
    let result = format_sql("SELECT id,name FROM users WHERE id=1", SqlDialect::Generic);
    assert!(result.success);
    assert!(!result.formatted_sql.is_empty());
}

#[test]
fn dialect_format_sql_snowflake() {
    let result = format_sql(
        "SELECT id,name FROM users WHERE id=1",
        SqlDialect::Snowflake,
    );
    assert!(result.success);
}

#[test]
fn dialect_format_sql_bigquery() {
    let result = format_sql("SELECT id,name FROM users WHERE id=1", SqlDialect::BigQuery);
    assert!(result.success);
}

#[test]
fn dialect_format_sql_postgres() {
    let result = format_sql("SELECT id,name FROM users WHERE id=1", SqlDialect::Postgres);
    assert!(result.success);
}

#[test]
fn dialect_format_invalid_sql() {
    let result = format_sql("SELECTZ broken", SqlDialect::Generic);
    // polyglot-sql may or may not treat this as an error; key assertion is no panic
    let _ = result;
}

// ═══════════════════════════════════════════════════════════════════════
// PII masking across dialects
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_masking_postgres_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Postgres).expect("should suggest");
    assert!(masking.contains("sha256"));
}

#[test]
fn dialect_masking_mysql_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::MySQL).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn dialect_masking_redshift_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Redshift).expect("should suggest");
    assert!(masking.contains("SHA2"));
}

#[test]
fn dialect_masking_generic_email() {
    let col = make_col("email", "VARCHAR");
    let masking = suggest_masking(&col, SqlDialect::Generic).expect("should suggest");
    assert!(masking.contains("MASKED"));
}

#[test]
fn dialect_masking_non_pii_returns_none() {
    let col = make_col("id", "INT");
    assert!(suggest_masking(&col, SqlDialect::Snowflake).is_none());
    assert!(suggest_masking(&col, SqlDialect::BigQuery).is_none());
    assert!(suggest_masking(&col, SqlDialect::Postgres).is_none());
}

// ═══════════════════════════════════════════════════════════════════════
// SqlDialect enum tests
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_display_snowflake() {
    assert_eq!(SqlDialect::Snowflake.to_string(), "snowflake");
}

#[test]
fn dialect_display_bigquery() {
    assert_eq!(SqlDialect::BigQuery.to_string(), "bigquery");
}

#[test]
fn dialect_display_databricks() {
    assert_eq!(SqlDialect::Databricks.to_string(), "databricks");
}

#[test]
fn dialect_display_postgres() {
    assert_eq!(SqlDialect::Postgres.to_string(), "postgres");
}

#[test]
fn dialect_display_mysql() {
    assert_eq!(SqlDialect::MySQL.to_string(), "mysql");
}

#[test]
fn dialect_display_generic() {
    assert_eq!(SqlDialect::Generic.to_string(), "generic");
}

// ═══════════════════════════════════════════════════════════════════════
// Cross-dialect safety
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_safety_info_schema_cross_dialect() {
    // information_schema probing should be caught regardless of dialect
    assert!(!is_safe("SELECT * FROM information_schema.tables"));
    assert!(!is_safe("SELECT * FROM information_schema.columns"));
}

#[test]
fn dialect_safety_sleep_cross_dialect() {
    // Sleep-based attacks across dialects
    assert!(!is_safe("SELECT * FROM users WHERE id = 1 AND SLEEP(5)"));
    assert!(!is_safe(
        "SELECT * FROM users WHERE id = 1 AND pg_sleep(10) IS NOT NULL"
    ));
}

#[test]
fn dialect_safety_stacked_queries_cross_dialect() {
    assert!(!is_safe("SELECT 1; DROP TABLE users"));
    assert!(!is_safe("SELECT 1; DELETE FROM users"));
}

#[test]
fn dialect_safety_comment_injection_cross_dialect() {
    assert!(!is_safe("SELECT * FROM users WHERE name = 'admin' --"));
}

// ═══════════════════════════════════════════════════════════════════════
// Transpile result serialization
// ═══════════════════════════════════════════════════════════════════════

#[test]
fn dialect_transpile_result_serializes() {
    let result = transpile(
        "SELECT id FROM users",
        SqlDialect::Generic,
        SqlDialect::Snowflake,
    );
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("original_sql"));
    assert!(json.contains("source_dialect"));
    assert!(json.contains("target_dialect"));
    assert!(json.contains("success"));
}

#[test]
fn dialect_prevalidate_result_serializes() {
    let result = prevalidate_syntax("SELECT id FROM users", SqlDialect::Generic);
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("valid"));
}

#[test]
fn dialect_format_result_serializes() {
    let result = format_sql("SELECT id FROM users", SqlDialect::Generic);
    let json = serde_json::to_string(&result).expect("should serialize");
    assert!(json.contains("formatted_sql"));
    assert!(json.contains("success"));
}
