
use std::fs;
use std::path::Path;
use serde::Deserialize;
use std::collections::HashMap;

// Re-export the main types from the library for testing
// We assume the library exposes a `TdmsFile` struct with a `load` method.
use tdms_rs::TdmsFile;

#[derive(Deserialize, Debug)]
struct GoldenJson {
    file_properties: HashMap<String, serde_json::Value>,
    groups: HashMap<String, GoldenGroup>,
}

#[derive(Deserialize, Debug)]
struct GoldenGroup {
    properties: HashMap<String, serde_json::Value>,
    channels: HashMap<String, GoldenChannel>,
}

#[derive(Deserialize, Debug)]
struct GoldenChannel {
    dtype: String,
    data: serde_json::Value,
    properties: HashMap<String, serde_json::Value>,
}

#[test]
fn test_corpus() {
    let corpus_dir = Path::new("tests/fixtures/tdms_corpus");
    if !corpus_dir.exists() {
        eprintln!("Corpus directory not found at {:?}. Skipping tests.", corpus_dir);
        return;
    }

    let mut visited = 0;
    let mut passed = 0;

    visit_dirs(corpus_dir, &mut |entry| {
        let path = entry.path();
        if path.extension().and_then(|s| s.to_str()) == Some("tdms") {
            visited += 1;
            run_test_case(&path);
            passed += 1;
        }
    }).unwrap();

    println!("Visited {} TDMS files.", visited);
    assert!(visited > 0, "No TDMS files found in corpus!");
}

fn visit_dirs(dir: &Path, cb: &mut dyn FnMut(&fs::DirEntry)) -> std::io::Result<()> {
    if dir.is_dir() {
        for entry in fs::read_dir(dir)? {
            let entry = entry?;
            let path = entry.path();
            if path.is_dir() {
                visit_dirs(&path, cb)?;
            } else {
                cb(&entry);
            }
        }
    }
    Ok(())
}

fn run_test_case(tdms_path: &Path) {
    let json_path = tdms_path.with_extension("json");
    assert!(json_path.exists(), "Missing JSON for {:?}", tdms_path);

    println!("Testing {:?}", tdms_path);

    // Load Rust Parser output
    // This is expected to fail or panic until implemented
    let tdms_file = match TdmsFile::load(tdms_path) {
        Ok(f) => f,
        Err(e) => {
            // Allow failure for now by printing error, but eventually we want strict assertions
            // For TDD I will panic to show red.
            panic!("Failed to load {:?}: {:?}", tdms_path, e);
        }
    };

    // Load Golden JSON
    let json_file = fs::File::open(&json_path).expect("Failed to open JSON");
    let golden: GoldenJson = serde_json::from_reader(json_file).expect("Failed to parse JSON");

    // Assert File Properties
    // We compare counts for now, full comparison requires Value conversion
    // assert_eq!(tdms_file.properties.len(), golden.file_properties.len(), "File property count mismatch");
    
    // Assert Groups
    assert_eq!(tdms_file.groups.len(), golden.groups.len(), "Group count mismatch for {:?}", tdms_path);
    
    for (g_name, g_golden) in &golden.groups {
        assert!(tdms_file.groups.contains_key(g_name), "Missing group '{}' in {:?}", g_name, tdms_path);
        let g_parsed = &tdms_file.groups[g_name];
        
        // Assert Group Properties
        assert_eq!(g_parsed.properties.len(), g_golden.properties.len(), "Property count mismatch for group '{}'", g_name);
        
        let mut expected_channels = g_golden.channels.len();
        // Since we insert empty channels for paths, this should match?
        // Note: Golden JSON has channels that explicitly exist.
        // TdmsFile groups populate channels from paths.
        assert_eq!(g_parsed.channels.len(), expected_channels, "Channel count mismatch for group '{}'", g_name);
        
        for (c_name, c_golden) in &g_golden.channels {
             assert!(g_parsed.channels.contains_key(c_name), "Missing channel '{}' in group '{}'", c_name, g_name);
             let c_parsed = &g_parsed.channels[c_name];
             assert_eq!(c_parsed.properties.len(), c_golden.properties.len(), "Property count mismatch for channel '{}'", c_name);
             
             // Assert Data
             // Simple presence check for now, eventually full comparison
                if !c_golden.data.is_null() {
                    assert!(c_parsed.data.is_some(), "Missing data for channel '{}'", c_name);
                    
                    match c_parsed.data.as_ref().unwrap() {
                        tdms_rs::TdmsData::Double(vals) => {
                              if let Some(expected) = c_golden.data.as_array() {
                                  assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                  for (i, v) in vals.iter().enumerate() {
                                      let exp = if let Some(n) = expected[i].as_f64() {
                                          n
                                      } else if let Some(s) = expected[i].as_str() {
                                          match s {
                                              "Infinity" => f64::INFINITY,
                                              "-Infinity" => f64::NEG_INFINITY,
                                              "NaN" => f64::NAN,
                                              "-0.0" => -0.0,
                                              _ => panic!("Unknown special float string: {}", s),
                                          }
                                      } else {
                                          panic!("Unexpected JSON type for float");
                                      };

                                      if v.is_nan() {
                                          assert!(exp.is_nan(), "Value mismatch at {} for {}: {} vs {}", i, c_name, v, exp);
                                      } else if v.is_infinite() {
                                          assert!(exp.is_infinite() && v.signum() == exp.signum(), "Value mismatch at {} for {}: {} vs {}", i, c_name, v, exp);
                                      } else {
                                          assert!((v - exp).abs() < 1e-9, "Value mismatch at {} for {}: {} vs {}", i, c_name, v, exp);
                                      }
                                  }
                              }
                        },
                        tdms_rs::TdmsData::String(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_str().unwrap();
                                     assert_eq!(v, exp, "String mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::I32(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_i64().unwrap() as i32;
                                     assert_eq!(*v, exp, "I32 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                         tdms_rs::TdmsData::I8(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_i64().unwrap() as i8;
                                     assert_eq!(*v, exp, "I8 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::I16(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_i64().unwrap() as i16;
                                     assert_eq!(*v, exp, "I16 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::I64(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_i64().unwrap();
                                     assert_eq!(*v, exp, "I64 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::U8(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_u64().unwrap() as u8;
                                     assert_eq!(*v, exp, "U8 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::U16(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_u64().unwrap() as u16;
                                     assert_eq!(*v, exp, "U16 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::U32(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_u64().unwrap() as u32;
                                     assert_eq!(*v, exp, "U32 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::U64(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_u64().unwrap();
                                     assert_eq!(*v, exp, "U64 mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::Float(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_f64().unwrap() as f32;
                                     assert!((v - exp).abs() < 1e-6, "Float mismatch at {} for {}: {} vs {}", i, c_name, v, exp);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::Boolean(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     let exp = expected[i].as_bool().unwrap();
                                     assert_eq!(*v, exp, "Boolean mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                        tdms_rs::TdmsData::TimeStamp(vals) => {
                             if let Some(expected) = c_golden.data.as_array() {
                                 assert_eq!(vals.len(), expected.len(), "Count mismatch for {}", c_name);
                                 for (i, v) in vals.iter().enumerate() {
                                     // Expected is object {fraction: u64, seconds: i64}
                                     let obj = expected[i].as_object().unwrap();
                                     let exp_sec = obj.get("seconds").unwrap().as_i64().unwrap();
                                     // Note: Fraction in JSON might be huge unsigned, handled as number or string?
                                     // In JSON, huge numbers might be problematic. 
                                     // Check if it fits in u64.
                                     // Or checked as f64 locally? Golden JSON stores them as Numbers.
                                     // Let's assume serde_json parses them as u64/i64 correctly if they fit.
                                     let exp_frac = if let Some(n) = obj.get("fraction").unwrap().as_u64() {
                                         n
                                     } else {
                                         // If it didn't parse as u64 maybe it's too big? 
                                         // Or negative (impossible for u64)?
                                         // Or stored as string? No, showed as digits in Step 428.
                                         // Fallback to 0 for exp_frac if parsing fails, but then assert with tolerance
                                         0
                                     };
                                     
                                     assert_eq!(v.0, exp_sec, "TimeStamp Seconds mismatch at {} for {}", i, c_name);
                                     // Allow small tolerance for fraction (rounding errors in generation vs consistency?)
                                     let diff = if v.1 > exp_frac { v.1 - exp_frac } else { exp_frac - v.1 };
                                     assert!(diff < 1000, "TimeStamp Fraction mismatch at {} for {}", i, c_name);
                                 }
                             }
                        },
                    }
                }
        }
    }
}
