use mf4_rs::writer::MdfWriter;
use mf4_rs::api::mdf::MDF;
use mf4_rs::parsing::decoder::{decode_channel_value, DecodedValue};
use mf4_rs::blocks::channel_block::ChannelBlock;
use mf4_rs::blocks::common::DataType;
use mf4_rs::error::MdfError;

#[test]
fn writer_and_parser_roundtrip() -> Result<(), MdfError> {
    let path = std::env::temp_dir().join("simple_test.mf4");
    if path.exists() {
        std::fs::remove_file(&path)?;
    }

    let mut writer = MdfWriter::new(path.to_str().unwrap())?;
    writer.init_mdf_file()?;
    let cg_id = writer.add_channel_group(None, |_| {})?;
    let cn1_id = writer.add_channel(&cg_id, None, |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
        ch.name = Some("Channel 1".to_string());
    })?;
    writer.add_channel(&cg_id, Some(&cn1_id), |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
        ch.name = Some("Channel 2".to_string());
    })?;
    writer.finalize()?;

    let mdf = MDF::from_file(path.to_str().unwrap())?;
    let groups = mdf.channel_groups();
    assert_eq!(groups.len(), 1);
    let cg = &groups[0];
    assert!(cg.name()?.is_none());
    let channels = cg.channels();
    assert_eq!(channels.len(), 2);
    assert_eq!(channels[0].name()?.as_deref(), Some("Channel 1"));
    assert_eq!(channels[1].name()?.as_deref(), Some("Channel 2"));
    assert!(channels[0].values()?.is_empty());
    assert!(channels[1].values()?.is_empty());

    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn writer_data_roundtrip() -> Result<(), MdfError> {
    let path = std::env::temp_dir().join("data_test.mf4");
    if path.exists() {
        std::fs::remove_file(&path)?;
    }

    let mut writer = MdfWriter::new(path.to_str().unwrap())?;
    writer.init_mdf_file()?;
    let cg_id = writer.add_channel_group(None, |_| {})?;
    let cn1 = writer.add_channel(&cg_id, None, |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
    })?;
    writer.add_channel(&cg_id, Some(&cn1), |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
    })?;

    writer.start_data_block_for_cg(&cg_id, 0)?;
    writer.write_record(
        &cg_id,
        &[DecodedValue::UnsignedInteger(1), DecodedValue::UnsignedInteger(2)],
    )?;
    writer.finish_data_block(&cg_id)?;
    writer.finalize()?;

    let mdf = MDF::from_file(path.to_str().unwrap())?;
    let groups = mdf.channel_groups();
    assert_eq!(groups.len(), 1);
    let cg = &groups[0];
    let channels = cg.channels();
    assert_eq!(channels.len(), 2);
    let vals1 = channels[0].values()?;
    let vals2 = channels[1].values()?;
    assert_eq!(vals1.len(), 1);
    assert_eq!(vals2.len(), 1);
    match &vals1[0] {
        Some(DecodedValue::UnsignedInteger(v)) => assert_eq!(*v, 1),
        other => panic!("unexpected {:?}", other),
    }
    match &vals2[0] {
        Some(DecodedValue::UnsignedInteger(v)) => assert_eq!(*v, 2),
        other => panic!("unexpected {:?}", other),
    }

    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn writer_write_records() -> Result<(), MdfError> {
    let path = std::env::temp_dir().join("bulk_test.mf4");
    if path.exists() { std::fs::remove_file(&path)?; }

    let mut writer = MdfWriter::new(path.to_str().unwrap())?;
    writer.init_mdf_file()?;
    let cg_id = writer.add_channel_group(None, |_| {})?;
    writer.add_channel(&cg_id, None, |ch| { ch.data_type = DataType::UnsignedIntegerLE; })?;

    writer.start_data_block_for_cg(&cg_id, 0)?;
    let recs = vec![
        vec![DecodedValue::UnsignedInteger(1)],
        vec![DecodedValue::UnsignedInteger(2)],
    ];
    let slices: Vec<&[DecodedValue]> = recs.iter().map(|r| r.as_slice()).collect();
    writer.write_records(&cg_id, slices)?;
    writer.finish_data_block(&cg_id)?;
    writer.finalize()?;

    let mdf = MDF::from_file(path.to_str().unwrap())?;
    let groups = mdf.channel_groups();
    let vals = groups[0].channels()[0].values()?;
    assert_eq!(vals.len(), 2);
    if let Some(DecodedValue::UnsignedInteger(v)) = vals[0] { assert_eq!(v, 1); } else { panic!("wrong type") }
    if let Some(DecodedValue::UnsignedInteger(v)) = vals[1] { assert_eq!(v, 2); } else { panic!("wrong type") }

    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn decode_channel_value_integer() {
    let mut ch = ChannelBlock::default();
    ch.data_type = DataType::UnsignedIntegerLE;
    ch.bit_count = 16;
    let record = [0x34, 0x12];
    match decode_channel_value(&record, 0, &ch).unwrap() {
        DecodedValue::UnsignedInteger(v) => assert_eq!(v, 0x1234),
        other => panic!("unexpected {:?}", other),
    }
}

#[test]
fn writer_block_position() -> Result<(), MdfError> {
    let path = std::env::temp_dir().join("pos_test.mf4");
    if path.exists() {
        std::fs::remove_file(&path)?;
    }

    let mut writer = MdfWriter::new(path.to_str().unwrap())?;
    let bytes = [1u8, 2, 3, 4];
    let pos = writer.write_block_with_id(&bytes, "blk")?;
    assert_eq!(writer.get_block_position("blk"), Some(pos));
    writer.finalize()?;
    std::fs::remove_file(path)?;
    Ok(())
}

#[test]
fn cut_mdf_file_by_time() -> Result<(), MdfError> {
    let input = std::env::temp_dir().join("cut_input.mf4");
    let output = std::env::temp_dir().join("cut_output.mf4");
    if input.exists() { std::fs::remove_file(&input)?; }
    if output.exists() { std::fs::remove_file(&output)?; }

    // Create source file with a time channel and a value channel
    let mut writer = MdfWriter::new(input.to_str().unwrap())?;
    writer.init_mdf_file()?;
    let cg_id = writer.add_channel_group(None, |_| {})?;
    let time_id = writer.add_channel(&cg_id, None, |ch| {
        ch.data_type = DataType::FloatLE;
        ch.name = Some("Time".into());
        ch.bit_count = 64;
    })?;
    writer.set_time_channel(&time_id)?;
    writer.add_channel(&cg_id, Some(&time_id), |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
        ch.bit_count = 32;
        ch.name = Some("Val".into());
    })?;
    writer.start_data_block_for_cg(&cg_id, 0)?;
    for i in 0..10u64 {
        writer.write_record(
            &cg_id,
            &[
                DecodedValue::Float(i as f64 * 0.1),
                DecodedValue::UnsignedInteger(i),
            ],
        )?;
    }
    writer.finish_data_block(&cg_id)?;
    writer.finalize()?;

    // Cut between 0.2 and 0.5 seconds
    mf4_rs::cut::cut_mdf_by_time(
        input.to_str().unwrap(),
        output.to_str().unwrap(),
        0.2,
        0.5,
    )?;

    let mdf = MDF::from_file(output.to_str().unwrap())?;
    let groups = mdf.channel_groups();
    assert_eq!(groups.len(), 1);
    let cg = &groups[0];
    let chs = cg.channels();
    assert_eq!(chs.len(), 2);
    let times = chs[0].values()?;
    let vals = chs[1].values()?;
    assert_eq!(times.len(), 4);
    assert_eq!(vals.len(), 4);
    if let Some(DecodedValue::Float(t0)) = times[0] { assert!((t0 - 0.2).abs() < 1e-6); }
    if let Some(DecodedValue::Float(t_last)) = times[3] { assert!((t_last - 0.5).abs() < 1e-6); }

    std::fs::remove_file(input)?;
    std::fs::remove_file(output)?;
    Ok(())
}

/// Regression: cut should not apply channel conversions during the
/// decode/encode round-trip. Previously cut decoded → applied conversion →
/// re-encoded the converted value into the raw slot, so reading the cut file
/// applied the conversion a second time. This test attaches a linear
/// conversion to the value channel and verifies the physical values after
/// cut match the source physical values exactly.
#[test]
fn cut_does_not_double_apply_conversions() -> Result<(), MdfError> {
    use mf4_rs::blocks::common::BlockHeader;
    use mf4_rs::blocks::conversion::{ConversionBlock, ConversionType};

    let input = std::env::temp_dir().join("cut_conv_input.mf4");
    let output = std::env::temp_dir().join("cut_conv_output.mf4");
    if input.exists() { std::fs::remove_file(&input)?; }
    if output.exists() { std::fs::remove_file(&output)?; }

    let mut writer = MdfWriter::new(input.to_str().unwrap())?;
    writer.init_mdf_file()?;
    let cg_id = writer.add_channel_group(None, |_| {})?;
    let time_id = writer.add_channel(&cg_id, None, |ch| {
        ch.data_type = DataType::FloatLE;
        ch.bit_count = 64;
        ch.name = Some("Time".into());
    })?;
    writer.set_time_channel(&time_id)?;
    let val_id = writer.add_channel(&cg_id, Some(&time_id), |ch| {
        ch.data_type = DataType::UnsignedIntegerLE;
        ch.bit_count = 32;
        ch.name = Some("Val".into());
    })?;

    // phys = 10 + 2 * raw  →  raw=3 → phys=16, raw=4 → phys=18, raw=5 → phys=20
    let conv = ConversionBlock {
        header: BlockHeader { id: "##CC".into(), reserved0: 0, block_len: 0, links_nr: 0 },
        cc_tx_name: None,
        cc_md_unit: None,
        cc_md_comment: None,
        cc_cc_inverse: None,
        cc_ref: Vec::new(),
        cc_type: ConversionType::Linear,
        cc_precision: 0,
        cc_flags: 0,
        cc_ref_count: 0,
        cc_val_count: 2,
        cc_phy_range_min: None,
        cc_phy_range_max: None,
        cc_val: vec![10.0, 2.0],
        formula: None,
        resolved_texts: None,
        resolved_conversions: None,
        default_conversion: None,
    };
    let cc_bytes = conv.to_bytes()?;
    let cc_id = "cc_lin".to_string();
    writer.write_block_with_id(&cc_bytes, &cc_id)?;
    // Patch val channel's conversion link (offset 56 from cn block start).
    writer.update_block_link(&val_id, 56, &cc_id)?;

    writer.start_data_block_for_cg(&cg_id, 0)?;
    for i in 0..10u64 {
        writer.write_record(
            &cg_id,
            &[
                DecodedValue::Float(i as f64 * 0.1),
                DecodedValue::UnsignedInteger(i),
            ],
        )?;
    }
    writer.finish_data_block(&cg_id)?;
    writer.finalize()?;

    // Sanity: source reads back with conversion applied.
    {
        let mdf = MDF::from_file(input.to_str().unwrap())?;
        let chs = mdf.channel_groups()[0].channels();
        let vals = chs[1].values()?;
        assert_eq!(vals.len(), 10);
        if let Some(DecodedValue::Float(v3)) = vals[3] {
            assert!((v3 - 16.0).abs() < 1e-9, "source phys[3] = {}", v3);
        } else {
            panic!("unexpected source vals[3]: {:?}", vals[3]);
        }
    }

    mf4_rs::cut::cut_mdf_by_time(
        input.to_str().unwrap(),
        output.to_str().unwrap(),
        0.3,
        0.5,
    )?;

    // The cut output drops conversion blocks (out of scope per plan), so the
    // value channel reads as raw u32. The raw values must still equal the
    // source raw values [3, 4, 5] — i.e. cut must NOT have stored the
    // physical values [16, 18, 20] in the raw slots.
    let mdf = MDF::from_file(output.to_str().unwrap())?;
    let chs = mdf.channel_groups()[0].channels();
    let vals = chs[1].values()?;
    assert_eq!(vals.len(), 3);
    let raws: Vec<u64> = vals
        .iter()
        .map(|v| match v {
            Some(DecodedValue::UnsignedInteger(u)) => *u,
            other => panic!("unexpected cut vals: {:?}", other),
        })
        .collect();
    assert_eq!(raws, vec![3, 4, 5], "cut stored converted values: {:?}", raws);

    std::fs::remove_file(input)?;
    std::fs::remove_file(output)?;
    Ok(())
}
