use libremetaverse::packets::{
    AgentThrottlePacket, Header, Packet, PacketType, UseCircuitCodePacket,
};
use libremetaverse::{Error, Helpers, PacketFrequency};
use libremetaverse_types::UUID;

fn header(frequency: PacketFrequency, id: u16) -> Header {
    Header {
        ack_list: None,
        appended_acks: false,
        frequency,
        id,
        reliable: true,
        resent: false,
        sequence: 0x0102_0304,
        zerocoded: false,
    }
}

fn header_bytes(header: &Header) -> Vec<u8> {
    let length = match header.frequency {
        PacketFrequency::High => 7,
        PacketFrequency::Medium => 8,
        PacketFrequency::Low => 10,
    };
    let mut bytes = vec![0_u8; length];
    let mut position = 0;
    header
        .to_bytes(&mut bytes, &mut position)
        .expect("encode header");
    assert_eq!(usize::try_from(position).unwrap(), bytes.len());
    bytes
}

#[test]
fn header_frequencies_match_the_reference_wire_bytes() {
    // Golden source: codegen/inputs/message_template.msg frequency rules and
    // LibreMetaverse.Tests/PacketTests.cs::HeaderFlags (translated in
    // tests/compat/tests/wire_semantics.rs::packet_header_flags).
    assert_eq!(
        header_bytes(&header(PacketFrequency::High, 0x2a)),
        [0x40, 1, 2, 3, 4, 0, 0x2a]
    );
    assert_eq!(
        header_bytes(&header(PacketFrequency::Medium, 0x2a)),
        [0x40, 1, 2, 3, 4, 0, 0xff, 0x2a]
    );
    assert_eq!(
        header_bytes(&header(PacketFrequency::Low, 0x1234)),
        [0x40, 1, 2, 3, 4, 0, 0xff, 0xff, 0x12, 0x34]
    );
}

#[test]
fn appended_acks_are_big_endian_and_removed_from_the_payload_boundary() {
    let mut value = header(PacketFrequency::High, 0x2a);
    value.appended_acks = true;
    value.ack_list = Some(vec![0x0102_0304, 0xa0b0_c0d0]);
    let mut bytes = header_bytes(&value);
    let mut ack_bytes = [0_u8; 9];
    let mut ack_position = 0;
    value
        .acks_to_bytes(&mut ack_bytes, &mut ack_position)
        .expect("encode ACKs");
    assert_eq!(ack_bytes, [1, 2, 3, 4, 0xa0, 0xb0, 0xc0, 0xd0, 2]);
    bytes.extend_from_slice(&ack_bytes);

    let mut position = 0;
    let mut packet_end = i32::try_from(bytes.len()).unwrap() - 1;
    let decoded = Header::build_header(bytes, &mut position, &mut packet_end).expect("decode");
    assert!(decoded.appended_acks);
    assert_eq!(decoded.ack_list, value.ack_list);
    assert_eq!(position, 7);
    assert_eq!(packet_end, 6);
}

#[test]
fn use_circuit_code_matches_the_reference_golden_packet() {
    // Golden schema: codegen/inputs/message_template.msg::UseCircuitCode
    // (Low 3, CircuitCode/U32 + SessionID/LLUUID + ID/LLUUID). Header behavior
    // is linked to the translated HeaderFlags test above.
    let session = (0_u8..16).collect::<Vec<_>>();
    let agent = (16_u8..32).collect::<Vec<_>>();
    let mut packet = UseCircuitCodePacket::new_with_constructor().expect("packet constructor");
    packet.circuit_code.code = 0x1122_3344;
    packet.circuit_code.session_id =
        UUID::new_with_bytes_int32(session.clone(), 0).expect("session UUID");
    packet.circuit_code.id = UUID::new_with_bytes_int32(agent.clone(), 0).expect("agent UUID");

    let encoded = packet.to_bytes_with_method().expect("encode packet");
    let mut expected = vec![
        0x40, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 3, 0x44, 0x33, 0x22, 0x11,
    ];
    expected.extend_from_slice(&session);
    expected.extend_from_slice(&agent);
    assert_eq!(encoded, expected);

    let mut position = 0;
    let decoded =
        UseCircuitCodePacket::new_with_bytes_int32(encoded.clone(), &mut position).expect("decode");
    assert_eq!(usize::try_from(position).unwrap(), encoded.len());
    assert_eq!(decoded.circuit_code.code, 0x1122_3344);
    assert_eq!(
        decoded.circuit_code.session_id.get_bytes().unwrap(),
        session
    );
    assert_eq!(decoded.circuit_code.id.get_bytes().unwrap(), agent);
    assert_eq!(decoded.to_bytes_with_method().unwrap(), encoded);
}

#[test]
fn malformed_and_unknown_packets_return_errors_without_panicking() {
    let valid = [0x40, 0, 0, 0, 0, 0, 0xff, 0xff, 0, 3];
    for length in 0..valid.len() {
        let bytes = valid[..length].to_vec();
        let outcome = std::panic::catch_unwind(|| {
            let mut end = i32::try_from(bytes.len()).unwrap() - 1;
            Packet::build_packet_with_bytes_int32_bytes(bytes.clone(), &mut end, vec![0; 64])
        });
        assert!(outcome.is_ok(), "truncation at {length} panicked");
        assert!(
            outcome.unwrap().is_err(),
            "truncation at {length} was accepted"
        );
    }

    let missing_payload = valid.to_vec();
    let mut end = i32::try_from(missing_payload.len()).unwrap() - 1;
    assert!(
        Packet::build_packet_with_bytes_int32_bytes(missing_payload, &mut end, vec![0; 64])
            .is_err()
    );

    let unknown = vec![0x40, 0, 0, 0, 0, 0, 0xfe];
    let mut end = i32::try_from(unknown.len()).unwrap() - 1;
    let Err(error) = Packet::build_packet_with_bytes_int32_bytes(unknown, &mut end, vec![0; 64])
    else {
        panic!("unknown packet ID was accepted");
    };
    assert!(matches!(error, Error::Parse { .. }));
}

#[test]
fn packet_factory_decodes_zerocoded_payloads() {
    let mut packet = AgentThrottlePacket::new_with_constructor().expect("packet constructor");
    packet.agent_data.circuit_code = 0x1122_3344;
    packet.throttle.gen_counter = 0x5566_7788;
    packet.throttle.throttles = vec![1, 0, 0, 2, 3];
    let raw = packet.to_bytes_with_method().expect("raw packet");
    let mut encoded = vec![0_u8; raw.len() * 2 + 2];
    let encoded_length = Helpers::zero_encode(
        Some(&raw),
        i32::try_from(raw.len()).unwrap(),
        Some(&mut encoded),
    )
    .expect("zero encode");
    encoded.truncate(usize::try_from(encoded_length).unwrap());

    let mut end = encoded_length - 1;
    let built =
        Packet::build_packet_with_bytes_int32_bytes(encoded, &mut end, vec![0_u8; raw.len()])
            .expect("packet factory");
    assert_eq!(built.type_, PacketType::AgentThrottle);
    assert_eq!(end, i32::try_from(raw.len()).unwrap() - 1);
}

#[test]
fn packet_id_dispatch_covers_known_and_unknown_ids() {
    assert_eq!(
        Packet::get_type(3, PacketFrequency::Low).expect("packet type"),
        PacketType::UseCircuitCode
    );
    assert_eq!(
        Packet::get_type(u16::MAX, PacketFrequency::High).expect("unknown type"),
        PacketType::Default
    );
    assert_eq!(Helpers::MSG_RELIABLE, 0x40);
}
