use dial9_trace_format::decoder::{DecodedFrame, Decoder};
use dial9_trace_format::encoder::Encoder;
use dial9_trace_format::schema::FieldDef;
use dial9_trace_format::types::{FieldType, FieldValue};

#[test]
fn full_round_trip() {
    let mut enc = Encoder::new();

    let poll_id = enc
        .register_schema(
            "PollStart",
            vec![
                FieldDef::new("worker", FieldType::Varint),
                FieldDef::new("task_id", FieldType::Varint),
            ],
        )
        .unwrap();
    let cpu_id = enc
        .register_schema(
            "CpuSample",
            vec![
                FieldDef::new("thread_name", FieldType::PooledString),
                FieldDef::new("frames", FieldType::StackFrames),
            ],
        )
        .unwrap();

    let thread_id = enc.intern_string("worker-0").unwrap();

    enc.write_event(
        &poll_id,
        &[
            FieldValue::Varint(1_000_000),
            FieldValue::Varint(0),
            FieldValue::Varint(42),
        ],
    )
    .unwrap();

    let frames = vec![0x5555_5555_1234u64, 0x5555_5555_0a00, 0x5555_5555_0800];
    enc.write_event(
        &cpu_id,
        &[
            FieldValue::Varint(1_000_100),
            FieldValue::PooledString(thread_id),
            FieldValue::StackFrames(frames.clone().into()),
        ],
    )
    .unwrap();

    let sym_name_id = enc.intern_string("my_function").unwrap();

    // Append symbol table as a schema-based event via the Encoder API.
    let sym_schema = enc
        .register_schema(
            "SymbolTableEntry",
            vec![
                FieldDef::new("base_addr", FieldType::Varint),
                FieldDef::new("size", FieldType::Varint),
                FieldDef::new("symbol_name", FieldType::PooledString),
            ],
        )
        .unwrap();
    enc.write_event(
        &sym_schema,
        &[
            FieldValue::Varint(0), // timestamp
            FieldValue::Varint(0x5555_5555_0000),
            FieldValue::Varint(0x2000),
            FieldValue::PooledString(sym_name_id),
        ],
    )
    .unwrap();

    let data = enc.finish();

    let mut dec = Decoder::new(&data).unwrap();
    assert_eq!(dec.version(), 1);

    let decoded = dec.decode_all();

    // 3 schemas(PollStart,CpuSample,SymbolTableEntry) + 1 pool("worker-0") + 1 poll event
    // + 1 cpu sample + 1 pool("my_function") + 1 symbol event = 8
    assert_eq!(decoded.len(), 8, "got: {decoded:#?}");

    assert!(matches!(&decoded[0], DecodedFrame::Schema(s) if s.name() == "PollStart"));
    assert!(matches!(&decoded[1], DecodedFrame::Schema(s) if s.name() == "CpuSample"));

    assert_eq!(dec.string_pool().get(thread_id), Some("worker-0"));
    assert_eq!(dec.string_pool().get(sym_name_id), Some("my_function"));

    // Verify poll event
    if let DecodedFrame::Event { values, .. } = &decoded[3] {
        assert_eq!(*values, vec![FieldValue::Varint(0), FieldValue::Varint(42)]);
    } else {
        panic!("expected event frame");
    }

    // Verify cpu sample with stack frames
    if let DecodedFrame::Event { values, .. } = &decoded[4] {
        assert_eq!(values[0], FieldValue::PooledString(thread_id));
        assert_eq!(values[1], FieldValue::StackFrames(frames.into()));
    } else {
        panic!("expected event frame");
    }

    // Verify symbol table event
    assert!(matches!(&decoded[6], DecodedFrame::Schema(s) if s.name() == "SymbolTableEntry"));
    if let DecodedFrame::Event { values, .. } = &decoded[7] {
        assert_eq!(values[0], FieldValue::Varint(0x5555_5555_0000));
        assert_eq!(values[1], FieldValue::Varint(0x2000));
        assert_eq!(values[2], FieldValue::PooledString(sym_name_id));
    } else {
        panic!("expected symbol table event");
    }
}

#[test]
fn round_trip_all_field_types() {
    let mut enc = Encoder::new();
    let tid = enc
        .register_schema(
            "AllTypes",
            vec![
                FieldDef::new("a", FieldType::Varint),
                FieldDef::new("b", FieldType::I64),
                FieldDef::new("c", FieldType::F64),
                FieldDef::new("d", FieldType::Bool),
                FieldDef::new("e", FieldType::String),
                FieldDef::new("f", FieldType::Bytes),
                FieldDef::new("h", FieldType::PooledString),
                FieldDef::new("i", FieldType::StackFrames),
            ],
        )
        .unwrap();

    let pool_id = enc.intern_string("test").unwrap();
    let values = vec![
        FieldValue::Varint(1_000_000), // timestamp
        FieldValue::Varint(u64::MAX),
        FieldValue::I64(i64::MIN),
        FieldValue::F64(std::f64::consts::E),
        FieldValue::Bool(false),
        FieldValue::String("hello".to_string()),
        FieldValue::Bytes(vec![0xDE, 0xAD]),
        FieldValue::PooledString(pool_id),
        FieldValue::StackFrames(vec![0xAAAA, 0xBBBB, 0xCCCC].into()),
    ];
    enc.write_event(&tid, &values).unwrap();
    let data = enc.finish();

    let mut dec = Decoder::new(&data).unwrap();
    let frames = dec.decode_all();
    let event = frames
        .iter()
        .find(|f| matches!(f, DecodedFrame::Event { .. }))
        .unwrap();
    if let DecodedFrame::Event {
        values: decoded_values,
        ..
    } = event
    {
        // Decoded values don't include the timestamp (it's in the header)
        assert_eq!(decoded_values, &values[1..]);
    }
}
