//! Test runner utilities for SQLGlot compatibility tests
#![allow(dead_code)]

use polyglot_sql::dialects::{Dialect, DialectType};
use polyglot_sql::generator::Generator;
use polyglot_sql::parser::Parser;

/// Run an identity test: parse SQL and verify it regenerates identically
pub fn identity_test(sql: &str) -> Result<(), String> {
    let ast = Parser::parse_sql(sql).map_err(|e| format!("Parse error: {}", e))?;

    if ast.is_empty() {
        return Err("No statements parsed".to_string());
    }

    let output = Generator::sql(&ast[0]).map_err(|e| format!("Generate error: {}", e))?;

    if output != sql {
        return Err(format!(
            "Mismatch:\n  input:  {}\n  output: {}",
            sql, output
        ));
    }

    Ok(())
}

/// Run an identity test with an optional expected output (for dialect-specific tests)
pub fn identity_test_with_expected(sql: &str, expected: Option<&str>) -> Result<(), String> {
    let ast = Parser::parse_sql(sql).map_err(|e| format!("Parse error: {}", e))?;

    if ast.is_empty() {
        return Err("No statements parsed".to_string());
    }

    let output = Generator::sql(&ast[0]).map_err(|e| format!("Generate error: {}", e))?;

    let expected_output = expected.unwrap_or(sql);

    if output != expected_output {
        return Err(format!(
            "Mismatch:\n  input:    {}\n  expected: {}\n  output:   {}",
            sql, expected_output, output
        ));
    }

    Ok(())
}

/// Run an identity test using a specific dialect for parsing and generation
/// Note: This applies dialect transforms before generating, matching Python SQLGlot behavior.
/// When the expected output contains newlines, pretty printing is enabled to match SQLGlot behavior.
pub fn dialect_identity_test(
    sql: &str,
    expected: Option<&str>,
    dialect: DialectType,
) -> Result<(), String> {
    let d = Dialect::get(dialect);

    let ast = d.parse(sql).map_err(|e| format!("Parse error: {}", e))?;

    if ast.is_empty() {
        return Err("No statements parsed".to_string());
    }

    // Apply dialect transforms before generating (matches Python SQLGlot behavior)
    let transformed = d
        .transform(ast[0].clone())
        .map_err(|e| format!("Transform error: {}", e))?;

    let expected_output = expected.unwrap_or(sql);

    // Use pretty printing if expected output contains newlines
    // This matches SQLGlot's behavior of formatting output when input is multi-line
    let use_pretty = expected_output.contains('\n');

    // Detect if identify mode should be used:
    // If expected has more quotes than input, the Python test likely used identify=True
    // This handles Athena/Presto/Trino tests that use identify mode for consistent quoting
    // NOTE: StarRocks and Exasol should NOT use identify mode - their reserved keywords
    // are already quoted by default, and identify mode over-quotes all identifiers.
    // TSQL and Fabric use bracket quoting [x] not double-quote quoting, so the
    // double-quote heuristic produces false positives on string literals containing quotes.
    let use_identify = if let Some(exp) = expected {
        // Skip identify mode for dialects that don't need it
        let skip_identify = matches!(
            dialect,
            DialectType::StarRocks
                | DialectType::Exasol
                | DialectType::TSQL
                | DialectType::Fabric
                | DialectType::BigQuery
                | DialectType::Snowflake
        );
        if skip_identify {
            false
        } else {
            let input_quotes = sql.matches('"').count() + sql.matches('`').count();
            let expected_quotes = exp.matches('"').count() + exp.matches('`').count();
            expected_quotes > input_quotes
        }
    } else {
        false
    };

    let output = if use_pretty {
        if use_identify {
            d.generate_pretty_with_identify(&transformed)
                .map_err(|e| format!("Generate error: {}", e))?
        } else {
            d.generate_pretty(&transformed)
                .map_err(|e| format!("Generate error: {}", e))?
        }
    } else {
        if use_identify {
            d.generate_with_identify(&transformed)
                .map_err(|e| format!("Generate error: {}", e))?
        } else {
            d.generate(&transformed)
                .map_err(|e| format!("Generate error: {}", e))?
        }
    };

    if output != expected_output {
        return Err(format!(
            "Mismatch:\n  input:    {}\n  expected: {}\n  output:   {}",
            sql, expected_output, output
        ));
    }

    Ok(())
}

/// Run a transpilation test: parse SQL from source dialect and generate for target dialect
pub fn transpile_test(
    sql: &str,
    source: DialectType,
    target: DialectType,
    expected: &str,
) -> Result<(), String> {
    let source_dialect = Dialect::get(source);

    // If the expected output contains newlines outside of string literals, use pretty-printed generation
    let use_pretty = {
        let mut in_string = false;
        let mut has_formatting_newline = false;
        let chars: Vec<char> = expected.chars().collect();
        let mut i = 0;
        while i < chars.len() {
            let c = chars[i];
            if c == '\'' && !in_string {
                in_string = true;
            } else if c == '\'' && in_string {
                // Check for escaped quote ('')
                if i + 1 < chars.len() && chars[i + 1] == '\'' {
                    i += 1; // skip next quote
                } else {
                    in_string = false;
                }
            } else if c == '\n' && !in_string {
                has_formatting_newline = true;
                break;
            }
            i += 1;
        }
        has_formatting_newline
    };

    let results = if use_pretty {
        source_dialect
            .transpile_to_pretty(sql, target)
            .map_err(|e| format!("Transpile error: {}", e))?
    } else {
        source_dialect
            .transpile_to(sql, target)
            .map_err(|e| format!("Transpile error: {}", e))?
    };

    if results.is_empty() {
        return Err("No statements transpiled".to_string());
    }

    if results[0] != expected {
        return Err(format!(
            "Mismatch:\n  input:    {} ({:?} -> {:?})\n  expected: {}\n  actual:   {}",
            sql, source, target, expected, results[0]
        ));
    }

    Ok(())
}

/// Parse a dialect name string to DialectType
pub fn parse_dialect(name: &str) -> Option<DialectType> {
    match name.to_lowercase().as_str() {
        "generic" | "" => Some(DialectType::Generic),
        "postgres" | "postgresql" => Some(DialectType::PostgreSQL),
        "mysql" => Some(DialectType::MySQL),
        "bigquery" => Some(DialectType::BigQuery),
        "snowflake" => Some(DialectType::Snowflake),
        "duckdb" => Some(DialectType::DuckDB),
        "sqlite" => Some(DialectType::SQLite),
        "hive" => Some(DialectType::Hive),
        "spark" => Some(DialectType::Spark),
        // "spark2" entries are skipped - Spark2 was merged into Spark
        // and some fixture expectations differ between spark2 and spark
        "trino" => Some(DialectType::Trino),
        "presto" => Some(DialectType::Presto),
        "redshift" => Some(DialectType::Redshift),
        "tsql" | "mssql" | "sqlserver" => Some(DialectType::TSQL),
        "oracle" => Some(DialectType::Oracle),
        "clickhouse" => Some(DialectType::ClickHouse),
        "databricks" => Some(DialectType::Databricks),
        "athena" => Some(DialectType::Athena),
        "teradata" => Some(DialectType::Teradata),
        "doris" => Some(DialectType::Doris),
        "starrocks" => Some(DialectType::StarRocks),
        "materialize" => Some(DialectType::Materialize),
        "risingwave" => Some(DialectType::RisingWave),
        "singlestore" | "memsql" => Some(DialectType::SingleStore),
        "cockroachdb" | "cockroach" => Some(DialectType::CockroachDB),
        "tidb" => Some(DialectType::TiDB),
        "dremio" => Some(DialectType::Dremio),
        "drill" => Some(DialectType::Drill),
        "druid" => Some(DialectType::Druid),
        "dune" => Some(DialectType::Dune),
        "exasol" => Some(DialectType::Exasol),
        "fabric" => Some(DialectType::Fabric),
        "solr" => Some(DialectType::Solr),
        "datafusion" | "arrow-datafusion" | "arrow_datafusion" => Some(DialectType::DataFusion),
        _ => None,
    }
}

/// Run a pretty-print test: parse SQL and verify it generates the expected formatted output
pub fn pretty_test(input: &str, expected: &str) -> Result<(), String> {
    let ast = Parser::parse_sql(input).map_err(|e| format!("Parse error: {}", e))?;

    if ast.is_empty() {
        return Err("No statements parsed".to_string());
    }

    let output = Generator::pretty_sql(&ast[0]).map_err(|e| format!("Generate error: {}", e))?;

    // Normalize line endings and trim
    let output_normalized = output.trim();
    let expected_normalized = expected.trim();

    if output_normalized != expected_normalized {
        return Err(format!(
            "Mismatch:\n  input:\n{}\n  expected:\n{}\n  output:\n{}",
            input, expected_normalized, output_normalized
        ));
    }

    Ok(())
}
