use libremetaverse::packets::{
    AgentMovementCompletePacketDataBlock, AgentThrottlePacketThrottleBlock, Packet,
    PacketAckPacket, PacketAckPacketPacketsBlock, PacketType, TestMessagePacket,
};
use libremetaverse_structured_data::{OSD, OSDMap};
use std::collections::HashMap;

fn map(values: impl IntoIterator<Item = (&'static str, OSD)>) -> OSD {
    OSD::Map(
        values
            .into_iter()
            .map(|(key, value)| (key.to_owned(), value))
            .collect::<HashMap<_, _>>(),
    )
}

#[test]
fn block_osd_matches_unsigned_and_missing_field_reference_semantics() {
    let mut block = AgentMovementCompletePacketDataBlock::new_with_constructor().unwrap();
    block.region_handle = 0x0102_0304_0506_0708;
    block.timestamp = 0x1122_3344;

    let encoded = block.to_osd().unwrap();
    assert_eq!(
        encoded.get("RegionHandle"),
        Some(OSD::Binary(vec![
            0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01
        ]))
    );
    assert_eq!(
        encoded.get("Timestamp"),
        Some(OSD::Binary(vec![0x44, 0x33, 0x22, 0x11]))
    );

    let partial = OSDMap::new_with_constructor().unwrap();
    partial
        .add_with_string_osd("Timestamp".to_owned(), OSD::Binary(vec![1, 2, 3]))
        .unwrap();
    block.from_osd(partial).unwrap();
    assert_eq!(block.timestamp, 0);
    assert_eq!(block.region_handle, 0x0102_0304_0506_0708);
}

#[test]
fn byte_fields_accept_osd_strings_as_utf8_like_the_reference_generator() {
    let mut block = AgentThrottlePacketThrottleBlock::new_with_constructor().unwrap();
    let source = OSDMap::new_with_constructor().unwrap();
    source
        .add_with_string_osd("Throttles".to_owned(), OSD::String("throttle".to_owned()))
        .unwrap();

    block.from_osd(source).unwrap();

    assert_eq!(block.throttles, b"throttle");
}

#[test]
fn packet_osd_replaces_variable_blocks_and_restores_fixed_block_counts() {
    let mut variable = PacketAckPacket::new_with_constructor().unwrap();
    let mut old = PacketAckPacketPacketsBlock::new_with_constructor().unwrap();
    old.id = 99;
    variable.packets.push(old);
    let variable_body = OSDMap::new_with_constructor().unwrap();
    variable_body
        .add_with_string_osd(
            "Packets".to_owned(),
            OSD::Array(vec![
                map([("ID", OSD::Binary(7_u32.to_le_bytes().to_vec()))]),
                OSD::Undefined,
            ]),
        )
        .unwrap();
    variable.packet_from_osd(variable_body).unwrap();
    assert_eq!(variable.packets.len(), 2);
    assert_eq!(variable.packets[0].id, 7);
    assert_eq!(variable.packets[1].id, 0);

    let mut fixed = TestMessagePacket::new_with_constructor().unwrap();
    fixed.neighbor_block.clear();
    let fixed_body = OSDMap::new_with_constructor().unwrap();
    fixed_body
        .add_with_string_osd(
            "NeighborBlock".to_owned(),
            OSD::Array(vec![
                map([("Test0", OSD::Binary(42_u32.to_le_bytes().to_vec()))]),
                OSD::String("not a block map".to_owned()),
            ]),
        )
        .unwrap();
    fixed.packet_from_osd(fixed_body).unwrap();
    assert_eq!(fixed.neighbor_block.len(), 4);
    assert_eq!(fixed.neighbor_block[0].test0, 42);
    assert!(
        fixed.neighbor_block[1..]
            .iter()
            .all(|block| block.test0 == 0)
    );
}

#[test]
fn osd_packet_factory_validates_known_bodies_and_rejects_unknown_names() {
    let body = OSDMap::new_with_constructor().unwrap();
    body.add_with_string_osd("Packets".to_owned(), OSD::Array(Vec::new()))
        .unwrap();

    let packet = Packet::build_packet_from_osd("PacketAck".to_owned(), body.clone())
        .unwrap()
        .expect("known packet");
    assert_eq!(packet.type_, PacketType::PacketAck);
    assert!(
        Packet::build_packet_from_osd("UnknownPacket".to_owned(), body)
            .unwrap()
            .is_none()
    );
}
