//! Integration tests for the DAG engine against production JSONL fixtures.
//!
//! Iterates 24,490 Rakuten Snowflake DML queries and validates:
//! - Parse rate (polyglot-sql + sqlparser fallback)
//! - `stmt_name` extraction accuracy
//! - Dependency structure correctness
//! - CTE name matching
//! - Schema enrichment

use altimate_core::dag::{TableSchema, build_dag_with_schema};
use polyglot_sql::dialects::DialectType;
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct DagFixture {
    #[allow(dead_code)]
    name: String,
    sql: String,
    #[allow(dead_code)]
    dialect: String,
    #[allow(dead_code)]
    database_name: Option<String>,
    #[allow(dead_code)]
    schema_name: Option<String>,
    #[allow(dead_code)]
    source_query_type: String,
    #[serde(default)]
    table_schema: HashMap<String, HashMap<String, String>>,
    expected: DagExpected,
}

#[derive(Debug, Deserialize)]
struct DagExpected {
    stmt_name: String,
    dependencies: HashMap<String, Vec<String>>,
    #[serde(default)]
    cte_definitions: HashMap<String, serde_json::Value>,
}

fn load_fixtures(path: &Path) -> Vec<DagFixture> {
    let content = fs::read_to_string(path)
        .unwrap_or_else(|e| panic!("failed to read fixture {}: {e}", path.display()));
    content
        .lines()
        .filter(|l| !l.trim().is_empty())
        .enumerate()
        .filter_map(|(i, line)| {
            serde_json::from_str(line)
                .map_err(|e| eprintln!("WARN: skipping line {}: {e}", i + 1))
                .ok()
        })
        .collect()
}

#[test]
fn dag_fixture_aggregate_metrics() {
    let fixture_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../experiments/lineage/visualizer/snowflake_rakuten.jsonl");

    if !fixture_path.exists() {
        eprintln!(
            "Skipping dag_fixture_aggregate_metrics: fixture not found at {}",
            fixture_path.display()
        );
        return;
    }

    let fixtures = load_fixtures(&fixture_path);
    let total = fixtures.len();
    assert!(total > 0, "Fixture file is empty");

    let mut parsed = 0usize;
    let mut stmt_name_match = 0usize;
    let mut dep_count_match = 0usize;
    let mut cte_match = 0usize;
    let mut schema_enriched = 0usize;
    let mut errors: Vec<String> = Vec::new();

    for (i, fixture) in fixtures.iter().enumerate() {
        // Build schema for enrichment
        let schema: TableSchema = fixture.table_schema.clone();
        let schema_ref = if schema.is_empty() {
            None
        } else {
            Some(&schema)
        };

        // Run DAG engine with 64MB stack (handled internally)
        let result = build_dag_with_schema(&fixture.sql, DialectType::Snowflake, schema_ref);

        match result {
            Ok(dag) => {
                parsed += 1;

                // Check stmt_name
                if dag.stmt_name == fixture.expected.stmt_name {
                    stmt_name_match += 1;
                }

                // Check dependency count (within ±2)
                let expected_dep_count = fixture.expected.dependencies.len();
                let actual_dep_count = dag.dependencies.len();
                if (actual_dep_count as i64 - expected_dep_count as i64).unsigned_abs() <= 2 {
                    dep_count_match += 1;
                }

                // Check CTE names
                let expected_ctes: Vec<String> = fixture
                    .expected
                    .cte_definitions
                    .keys()
                    .filter(|k| k.as_str() != "__final_select__")
                    .filter(|k| {
                        fixture
                            .expected
                            .cte_definitions
                            .get(*k)
                            .and_then(|v| v.get("type"))
                            .and_then(|t| t.as_str())
                            == Some("cte")
                    })
                    .cloned()
                    .collect();

                if !expected_ctes.is_empty() {
                    // Case-insensitive comparison: Snowflake normalizes to uppercase
                    // but polyglot-sql preserves original SQL casing.
                    let matched = expected_ctes
                        .iter()
                        .all(|cte_name| dag.ctes.iter().any(|c| c.eq_ignore_ascii_case(cte_name)));
                    if matched {
                        cte_match += 1;
                    }
                } else {
                    // No CTEs expected — trivially matches
                    cte_match += 1;
                }

                // Check schema enrichment
                if schema_ref.is_some() {
                    let has_enriched = dag
                        .nodes
                        .values()
                        .any(|n| n.kind == "table" && !n.columns.is_empty());
                    if has_enriched {
                        schema_enriched += 1;
                    }
                }
            }
            Err(e) => {
                if errors.len() < 10 {
                    errors.push(format!("line {}: {} — {}", i + 1, fixture.name, e));
                }
            }
        }
    }

    let parse_rate = parsed as f64 / total as f64 * 100.0;
    let stmt_rate = if parsed > 0 {
        stmt_name_match as f64 / parsed as f64 * 100.0
    } else {
        0.0
    };
    let dep_rate = if parsed > 0 {
        dep_count_match as f64 / parsed as f64 * 100.0
    } else {
        0.0
    };
    let cte_rate = if parsed > 0 {
        cte_match as f64 / parsed as f64 * 100.0
    } else {
        0.0
    };
    let schema_with_data = fixtures
        .iter()
        .filter(|f| !f.table_schema.is_empty())
        .count();
    let schema_rate = if schema_with_data > 0 {
        schema_enriched as f64 / schema_with_data as f64 * 100.0
    } else {
        0.0
    };

    eprintln!("\n=== DAG Fixture Test Results ===");
    eprintln!("Total fixtures:      {total}");
    eprintln!("Parsed:              {parsed} ({parse_rate:.1}%)");
    eprintln!("stmt_name match:     {stmt_name_match} ({stmt_rate:.1}%)");
    eprintln!("Dep count ±2:        {dep_count_match} ({dep_rate:.1}%)");
    eprintln!("CTE names match:     {cte_match} ({cte_rate:.1}%)");
    eprintln!("Schema enriched:     {schema_enriched}/{schema_with_data} ({schema_rate:.1}%)");

    if !errors.is_empty() {
        eprintln!("\nSample errors:");
        for e in &errors {
            eprintln!("  {e}");
        }
    }
    eprintln!("================================\n");

    // Assert minimum thresholds
    assert!(
        parse_rate >= 95.0,
        "Parse rate {parse_rate:.1}% below 95% threshold"
    );
    assert!(
        stmt_rate >= 90.0,
        "stmt_name match rate {stmt_rate:.1}% below 90% threshold"
    );
    assert!(
        dep_rate >= 60.0,
        "Dep count match rate {dep_rate:.1}% below 60% threshold"
    );
    assert!(
        cte_rate >= 80.0,
        "CTE name match rate {cte_rate:.1}% below 80% threshold"
    );
    if schema_with_data > 0 {
        assert!(
            schema_rate >= 50.0,
            "Schema enrichment rate {schema_rate:.1}% below 50% threshold"
        );
    }
}
