//! Analyze "wrong table" mismatches — cases where we have the right column name
//! but attribute it to the wrong source table.

use altimate_core::lineage::column_lineage;
use altimate_core::schema::types::SqlDialect;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
use std::io::BufRead;
use std::path::Path;

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

fn schema_from_json(value: &serde_json::Value) -> HashMap<String, serde_json::Value> {
    match value {
        serde_json::Value::Object(map) => map.iter().map(|(k, v)| (k.clone(), v.clone())).collect(),
        _ => HashMap::new(),
    }
}

fn normalize(map: &HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
    map.iter()
        .map(|(k, v)| {
            let mut sorted = v.clone();
            sorted.sort();
            sorted.dedup();
            (k.clone(), sorted)
        })
        .collect()
}

fn normalize_to_short(map: &HashMap<String, Vec<String>>) -> HashMap<String, Vec<String>> {
    map.iter()
        .map(|(k, v)| {
            let normalized: Vec<String> = v
                .iter()
                .map(|ref_str| {
                    let parts: Vec<&str> =
                        ref_str.split('.').map(|p| p.trim_matches('"')).collect();
                    if parts.len() >= 3 {
                        let table = parts[parts.len() - 2];
                        let col = parts[parts.len() - 1];
                        format!("\"{table}\".\"{col}\"")
                    } else {
                        ref_str.clone()
                    }
                })
                .collect();
            let mut sorted = normalized;
            sorted.sort();
            sorted.dedup();
            (k.clone(), sorted)
        })
        .collect()
}

fn table_of(ref_str: &str) -> String {
    let parts: Vec<&str> = ref_str.split('.').map(|p| p.trim_matches('"')).collect();
    if parts.len() >= 2 {
        parts[parts.len() - 2].to_uppercase()
    } else {
        String::new()
    }
}

fn col_of(ref_str: &str) -> String {
    let parts: Vec<&str> = ref_str.split('.').map(|p| p.trim_matches('"')).collect();
    parts.last().unwrap_or(&"").to_uppercase()
}

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

    let mut total_wrong_table_cols = 0u64;
    let mut total_wrong_table_queries = 0u64;

    // Pattern: what table names do we produce vs expected?
    // Track (expected_table, actual_table) pairs
    let mut table_pair_freq: HashMap<(String, String), u64> = HashMap::new();

    // Is the actual table a CTE/subquery alias or a real schema table?
    let mut actual_is_schema_table = 0u64;
    let mut actual_is_non_schema = 0u64;

    // Samples
    let mut samples: Vec<(String, String, String, String, String, String)> = Vec::new(); // (name, col, expected_ref, actual_ref, sql_prefix, schema_tables)

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

        let schema = schema_from_json(&case.schema);
        let schema_tables: HashSet<String> = schema.keys().map(|t| t.to_uppercase()).collect();

        let result = match column_lineage(&case.sql, dialect, &schema, None) {
            Ok(r) => r,
            Err(_) => continue,
        };

        let actual = normalize_to_short(&result.column_dict);
        let expected = normalize(&case.expected);

        if actual == expected {
            continue;
        }

        let mut query_has_wrong_table = false;

        for (col, exp_sources) in &expected {
            let act_sources = match actual.get(col) {
                Some(s) => s,
                None => continue,
            };

            // Check for wrong table: same column name, different table
            for exp_ref in exp_sources {
                let exp_col = col_of(exp_ref);
                let exp_table = table_of(exp_ref);

                // Is this expected source present in actual?
                if act_sources.contains(exp_ref) {
                    continue; // correct
                }

                // Check if actual has same column from different table
                for act_ref in act_sources {
                    let act_col = col_of(act_ref);
                    let act_table = table_of(act_ref);

                    if act_col == exp_col && act_table != exp_table {
                        total_wrong_table_cols += 1;
                        query_has_wrong_table = true;

                        let pair = (exp_table.clone(), act_table.clone());
                        *table_pair_freq.entry(pair).or_default() += 1;

                        if schema_tables.contains(&act_table) {
                            actual_is_schema_table += 1;
                        } else {
                            actual_is_non_schema += 1;
                        }

                        if samples.len() < 10 {
                            let sql_prefix: String = case.sql.chars().take(100).collect();
                            let schema_str: String = schema_tables
                                .iter()
                                .take(5)
                                .cloned()
                                .collect::<Vec<_>>()
                                .join(", ");
                            samples.push((
                                case.name.clone(),
                                col.clone(),
                                exp_ref.clone(),
                                act_ref.clone(),
                                sql_prefix,
                                schema_str,
                            ));
                        }
                        break; // found the wrong table match for this expected ref
                    }
                }
            }
        }

        if query_has_wrong_table {
            total_wrong_table_queries += 1;
        }
    }

    println!("=== Wrong Table Analysis ===");
    println!("Queries with wrong table:  {total_wrong_table_queries}");
    println!("Per-column wrong table:    {total_wrong_table_cols}");
    println!();
    println!("Actual table is schema table: {actual_is_schema_table}");
    println!("Actual table is non-schema:   {actual_is_non_schema}");
    println!();

    println!("=== Top 20 (Expected → Actual) Table Pairs ===");
    let mut pairs: Vec<_> = table_pair_freq.into_iter().collect();
    pairs.sort_by(|a, b| b.1.cmp(&a.1));
    for ((exp, act), count) in pairs.iter().take(20) {
        println!("  {exp:<30} → {act:<30}  {count:>5}");
    }

    println!();
    println!("=== Samples ===");
    for (i, (name, col, exp_ref, act_ref, sql, schema_tables)) in samples.iter().enumerate() {
        println!("--- Sample {} ---", i + 1);
        println!("  Query:    {name}");
        println!("  Column:   {col}");
        println!("  Expected: {exp_ref}");
        println!("  Actual:   {act_ref}");
        println!("  Schema:   {schema_tables}");
        println!("  SQL:      {sql}...");
        println!();
    }
}
