//! Fix ground truth fixture by merging validated superset sources.
//!
//! For 5 accepted cases where our engine correctly emits extra sources
//! (from tables NOT in the provided schema), merge those extras into the
//! expected field so the fixture matches reality.
//!
//! Strategy: read ALL lines, patch only the 5 target cases, write back.

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

/// The 5 accepted "correct superset" case names.
const TARGET_NAMES: &[&str] = &[
    "sf_insert_e5739d2c5883",
    "sf_insert_08621d4cfd84",
    "sf_insert_56fdbc562949",
    "sf_create_table_as_select_f38c788b1a36",
    "sf_insert_9c8abd4ed0e1",
];

fn main() {
    let targets: HashSet<&str> = TARGET_NAMES.iter().copied().collect();
    let dialect = SqlDialect::Snowflake;

    let fixture_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("../../tests/fixtures/lineage/snowflake_ground_truth.jsonl");
    eprintln!("Reading: {}", fixture_path.display());

    // Phase 1: Read ALL lines into memory
    let file = std::fs::File::open(&fixture_path).expect("cannot open ground truth fixture");
    let reader = std::io::BufReader::with_capacity(2 * 1024 * 1024, file);
    let lines: Vec<String> = reader.lines().map(|l| l.expect("read error")).collect();
    eprintln!("Loaded {} lines", lines.len());

    // Phase 2: Process each line, patching target cases
    let mut output_lines: Vec<String> = Vec::with_capacity(lines.len());
    let mut cases_updated = 0u32;
    let mut total_sources_added = 0u32;

    for (i, line) in lines.iter().enumerate() {
        if line.trim().is_empty() {
            output_lines.push(line.clone());
            continue;
        }

        // Parse as generic JSON Value to preserve all fields and ordering
        let mut record: serde_json::Value = match serde_json::from_str(line) {
            Ok(v) => v,
            Err(_) => {
                output_lines.push(line.clone());
                continue;
            }
        };

        let name = match record.get("name").and_then(|v| v.as_str()) {
            Some(n) => n.to_string(),
            None => {
                output_lines.push(line.clone());
                continue;
            }
        };

        if !targets.contains(name.as_str()) {
            // Not a target case — pass through unchanged
            output_lines.push(line.clone());
            continue;
        }

        // Extract sql and schema for lineage
        let sql = match record.get("sql").and_then(|v| v.as_str()) {
            Some(s) => s.to_string(),
            None => {
                eprintln!("  [{name}] SKIP: no sql field");
                output_lines.push(line.clone());
                continue;
            }
        };

        let schema_val = match record.get("schema") {
            Some(v) => v.clone(),
            None => {
                eprintln!("  [{name}] SKIP: no schema field");
                output_lines.push(line.clone());
                continue;
            }
        };

        let schema_map: HashMap<String, serde_json::Value> =
            match serde_json::from_value(schema_val) {
                Ok(m) => m,
                Err(e) => {
                    eprintln!("  [{name}] SKIP: schema parse error: {e}");
                    output_lines.push(line.clone());
                    continue;
                }
            };

        // Run column lineage
        let result = match column_lineage(&sql, dialect, &schema_map, None) {
            Ok(r) => r,
            Err(e) => {
                eprintln!("  [{name}] SKIP: lineage error: {e}");
                output_lines.push(line.clone());
                continue;
            }
        };

        // Merge actual sources into expected (union per column)
        let expected_obj = match record.get_mut("expected").and_then(|v| v.as_object_mut()) {
            Some(obj) => obj,
            None => {
                eprintln!("  [{name}] SKIP: expected is not an object");
                output_lines.push(line.clone());
                continue;
            }
        };

        let mut sources_added_this_case = 0u32;

        for (col, actual_sources) in &result.column_dict {
            // Get or skip columns not in expected (we only ADD sources, never add columns)
            let exp_arr = match expected_obj.get_mut(col).and_then(|v| v.as_array_mut()) {
                Some(arr) => arr,
                None => continue, // column not in expected, skip
            };

            // Collect existing expected sources
            let existing: BTreeSet<String> = exp_arr
                .iter()
                .filter_map(|v| v.as_str().map(|s| s.to_string()))
                .collect();

            // Find extras from actual that aren't in expected
            let actual_set: BTreeSet<String> = actual_sources.iter().cloned().collect();
            let extras: Vec<String> = actual_set.difference(&existing).cloned().collect();

            if !extras.is_empty() {
                sources_added_this_case += extras.len() as u32;
                for extra in extras {
                    exp_arr.push(serde_json::Value::String(extra));
                }
                // Sort the array for deterministic output
                exp_arr.sort_by(|a, b| {
                    let sa = a.as_str().unwrap_or("");
                    let sb = b.as_str().unwrap_or("");
                    sa.cmp(sb)
                });
            }
        }

        if sources_added_this_case > 0 {
            cases_updated += 1;
            total_sources_added += sources_added_this_case;
            println!("  UPDATED: {name} (+{sources_added_this_case} source refs)",);
        } else {
            println!("  NO-OP:   {name} (actual already matches expected)");
        }

        // Serialize back as single-line JSON
        let patched_line = serde_json::to_string(&record).expect("serialization failed");
        output_lines.push(patched_line);

        if (i + 1) % 5000 == 0 {
            eprintln!("  ... processed {}/{}", i + 1, lines.len());
        }
    }

    // Phase 3: Write ALL lines back to the fixture file
    eprintln!(
        "Writing {} lines to: {}",
        output_lines.len(),
        fixture_path.display()
    );
    let out_file = std::fs::File::create(&fixture_path).expect("cannot create output fixture file");
    let mut writer = std::io::BufWriter::with_capacity(4 * 1024 * 1024, out_file);
    for line in &output_lines {
        writeln!(writer, "{}", line).expect("write error");
    }
    writer.flush().expect("flush error");

    // Summary
    println!();
    println!("=== Summary ===");
    println!("Total lines:        {}", output_lines.len());
    println!("Target cases:       {}", TARGET_NAMES.len());
    println!("Cases updated:      {cases_updated}");
    println!("Source refs added:   {total_sources_added}");
    println!("Output written to:  {}", fixture_path.display());
}
