//! Detailed validation report for the 22 "correct superset" cases.
//!
//! These are cases where our engine returns extra sources that ALL come from
//! tables NOT present in the schema.  This means Snowflake ACCESS_USAGE
//! couldn't trace them (the ground truth is incomplete), so our results are
//! actually more accurate than the fixture expects.

use altimate_core::lineage::column_lineage;
use altimate_core::schema::types::SqlDialect;
use std::collections::{BTreeSet, HashMap, HashSet};
use std::io::BufRead;

/// The 22 known "correct superset" case names.
const CORRECT_SUPERSET_NAMES: &[&str] = &[
    "sf_insert_e5739d2c5883",
    "sf_insert_08621d4cfd84",
    "sf_insert_56fdbc562949",
    "sf_create_table_as_select_f38c788b1a36",
    "sf_insert_9c8abd4ed0e1",
    "sf_create_table_as_select_5ea36af5a999",
    "sf_create_table_as_select_81d7da9f18ec",
    "sf_create_table_as_select_cf0c9c00fbfd",
    "sf_create_table_as_select_53168677e8a9",
    "sf_insert_59d9e917e7f5",
    "sf_insert_94feb49ccd4e",
    "sf_insert_096646f79ac3",
    "sf_insert_0390b943e1f6",
    "sf_insert_21b86b6b541d",
    "sf_insert_d9cd116a441d",
    "sf_insert_447e8d444b32",
    "sf_insert_b3ccb83cb5cd",
    "sf_insert_3972cb688ba9",
    "sf_insert_17476fd313b2",
    "sf_insert_ad325093e3b5",
    "sf_insert_ef1b6474b9a0",
    "sf_create_table_as_select_ef1ea2ceb5ec",
];

#[derive(serde::Deserialize)]
struct Case {
    name: String,
    sql: String,
    schema: serde_json::Value,
    expected: HashMap<String, Vec<String>>,
}

/// Extract the set of table names from the schema JSON object (uppercased).
fn schema_tables(schema: &serde_json::Value) -> HashSet<String> {
    let mut tables = HashSet::new();
    if let serde_json::Value::Object(map) = schema {
        for k in map.keys() {
            tables.insert(k.to_uppercase());
        }
    }
    tables
}

/// Extract table name from a qualified reference like `"TABLE"."COL"`.
fn extract_table_from_ref(source_ref: &str) -> Option<String> {
    let stripped = source_ref.replace('"', "");
    let parts: Vec<&str> = stripped.split('.').collect();
    if parts.len() >= 2 {
        Some(parts[parts.len() - 2].to_uppercase())
    } else {
        None
    }
}

fn main() {
    let target_names: HashSet<&str> = CORRECT_SUPERSET_NAMES.iter().copied().collect();

    let fixture_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests/fixtures/lineage/snowflake_ground_truth.jsonl");
    let file = std::fs::File::open(&fixture_path).expect("cannot open ground truth fixture");
    let reader = std::io::BufReader::with_capacity(1024 * 1024, file);
    let dialect = SqlDialect::Snowflake;

    let mut found = 0;

    for line in reader.lines() {
        let line = line.unwrap();
        if line.trim().is_empty() {
            continue;
        }
        let case: Case = match serde_json::from_str(&line) {
            Ok(c) => c,
            Err(_) => continue,
        };

        if !target_names.contains(case.name.as_str()) {
            continue;
        }
        found += 1;

        // Collect schema info
        let schema_table_set = schema_tables(&case.schema);
        let schema_table_names: BTreeSet<String> = schema_table_set.iter().cloned().collect();
        let schema_map: HashMap<String, serde_json::Value> =
            match serde_json::from_value(case.schema.clone()) {
                Ok(s) => s,
                Err(e) => {
                    println!("=== Case: {} ===", case.name);
                    println!("ERROR: failed to parse schema: {e}\n");
                    continue;
                }
            };

        // Run lineage engine
        let result = match column_lineage(&case.sql, dialect, &schema_map, None) {
            Ok(r) => r,
            Err(e) => {
                println!("=== Case: {} ===", case.name);
                println!("ERROR: lineage failed: {e}\n");
                continue;
            }
        };

        // Normalize actual and expected
        let actual: HashMap<String, BTreeSet<String>> = result
            .column_dict
            .iter()
            .map(|(k, v)| (k.clone(), v.iter().cloned().collect()))
            .collect();
        let expected: HashMap<String, BTreeSet<String>> = case
            .expected
            .iter()
            .map(|(k, v)| (k.clone(), v.iter().cloned().collect()))
            .collect();

        // Print header
        let sql_preview: String = case.sql.chars().take(500).collect();
        println!("=== Case: {} ===", case.name);
        println!("Schema tables: {:?}", schema_table_names);
        println!("SQL (first 500 chars): {}", sql_preview);
        println!();

        // Find columns with EXTRA sources
        let mut has_extras = false;
        let mut all_cols: BTreeSet<String> = BTreeSet::new();
        all_cols.extend(expected.keys().cloned());
        all_cols.extend(actual.keys().cloned());

        for col in &all_cols {
            let exp: BTreeSet<String> = expected.get(col).cloned().unwrap_or_default();
            let act: BTreeSet<String> = actual.get(col).cloned().unwrap_or_default();

            let extra: BTreeSet<String> = act.difference(&exp).cloned().collect();
            if extra.is_empty() {
                continue;
            }
            has_extras = true;

            println!("  Column: {col}");
            println!("    Expected: {:?}", exp.iter().collect::<Vec<_>>());
            println!("    Actual:   {:?}", act.iter().collect::<Vec<_>>());
            println!("    Extra:    {:?}", extra.iter().collect::<Vec<_>>());

            for e in &extra {
                if let Some(table) = extract_table_from_ref(e) {
                    let in_schema = schema_table_set.contains(&table);
                    if in_schema {
                        println!("    Table of extra source \"{e}\": {table} (IN schema ✗)");
                    } else {
                        println!("    Table of extra source \"{e}\": {table} (NOT in schema ✓)");
                    }
                } else {
                    println!("    Table of extra source \"{e}\": <could not extract table>");
                }
            }
            println!();
        }

        if !has_extras {
            println!("  (no extra sources -- exact match or subset)");
            println!();
        }

        println!();

        if found == target_names.len() {
            break; // all 22 found, stop scanning
        }
    }

    println!("=== Summary ===");
    println!("Found {found} / {} target cases", target_names.len());
}
