#![allow(clippy::await_holding_lock)] // The process-wide guard serializes socket integration tests.
#![allow(clippy::float_cmp)] // Wire fixtures use exactly representable values.
#![allow(clippy::items_after_statements)] // Local constants stay next to their concurrency scenario.
#![allow(clippy::too_many_lines)] // End-to-end fixtures keep each protocol exchange contiguous.

use libremetaverse::http::DownloadManager;
use libremetaverse::interfaces::IMessage;
use libremetaverse::messages::linden::{
    EnableSimulatorMessage, EnableSimulatorMessageSimulatorInfoBlock,
};
use libremetaverse::packets::{
    AgentThrottlePacket, CompleteAgentMovementPacket, DisableSimulatorPacket, LogoutReplyPacket,
    LogoutReplyPacketInventoryDataBlock, LogoutRequestPacket, Packet, PacketAckPacket,
    PacketAckPacketPacketsBlock, PacketType, RegionHandshakePacket, RegionHandshakeReplyPacket,
    SimStatsPacket, SimStatsPacketStatBlock, StartPingCheckPacket, UseCircuitCodePacket,
};
use libremetaverse::{
    AgentThrottle, CapsEventDictionary, CapsEventQueueCallback, GridClient, Helpers,
    HttpCapsClient, InventoryItem, LoginState, LoginStatus, NetworkManager,
    NetworkManagerDisconnectType, NetworkManagerLoginResponseCallback, PacketEventDictionary,
    Simulator,
};
use libremetaverse_structured_data::{OSD, OSDMap, OSDParser};
use libremetaverse_types::compat::{
    CancellationTokenSource, EventHandler, HttpMessageHandler, HttpRequest, HttpResponse, Uri,
};
use libremetaverse_types::{AssetType, Error, UUID, Utils};
use std::collections::{BTreeMap, HashMap};
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::sync::{Arc, Barrier, Mutex, MutexGuard, OnceLock, Weak};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};

fn block_on<F: std::future::Future>(future: F) -> F::Output {
    use std::task::{Context, Poll, Wake, Waker};

    struct ThreadWake(thread::Thread);

    impl Wake for ThreadWake {
        fn wake(self: Arc<Self>) {
            self.0.unpark();
        }
    }

    let waker = Waker::from(Arc::new(ThreadWake(thread::current())));
    let mut context = Context::from_waker(&waker);
    let mut future = std::pin::pin!(future);
    loop {
        match future.as_mut().poll(&mut context) {
            Poll::Ready(output) => return output,
            Poll::Pending => thread::park(),
        }
    }
}

fn loopback(port: u16) -> SocketAddr {
    SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port)
}

fn simulator(client: &GridClient) -> Simulator {
    Simulator::new(
        client.clone(),
        loopback(13000),
        0x1000,
        Some(256),
        Some(256),
    )
    .expect("simulator")
}

fn base_packet(packet_type: PacketType) -> Packet {
    Packet::build_packet_with_packet_type(packet_type).expect("base packet")
}

fn wait_until(mut condition: impl FnMut() -> bool) {
    let deadline = Instant::now() + Duration::from_secs(2);
    while !condition() {
        assert!(Instant::now() < deadline, "condition timed out");
        thread::sleep(Duration::from_millis(2));
    }
}

fn network_test_guard() -> MutexGuard<'static, ()> {
    static NETWORK_TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
    NETWORK_TEST_LOCK
        .get_or_init(|| Mutex::new(()))
        .lock()
        .unwrap_or_else(std::sync::PoisonError::into_inner)
}

#[test]
fn packet_callbacks_preserve_filtering_order_async_policy_and_reentrancy() {
    let client = GridClient::new().expect("client");
    let sim = simulator(&client);
    let events = Arc::new(PacketEventDictionary::new(client).expect("packet events"));
    let order = Arc::new(Mutex::new(Vec::new()));

    let default_order = Arc::clone(&order);
    events
        .register_event(
            PacketType::Default,
            Arc::new(move |_| default_order.lock().unwrap().push("default")),
            false,
        )
        .unwrap();
    let specific_order = Arc::clone(&order);
    events
        .register_event(
            PacketType::StartPingCheck,
            Arc::new(move |_| specific_order.lock().unwrap().push("specific")),
            false,
        )
        .unwrap();

    events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim.clone(),
        )
        .unwrap();
    assert_eq!(*order.lock().unwrap(), ["default", "specific"]);

    order.lock().unwrap().clear();
    events
        .invoke_raise_event(
            PacketType::CompletePingCheck,
            base_packet(PacketType::CompletePingCheck),
            sim.clone(),
        )
        .unwrap();
    assert_eq!(*order.lock().unwrap(), ["default"]);

    let reentrant_count = Arc::new(AtomicUsize::new(0));
    let weak_events: Weak<PacketEventDictionary> = Arc::downgrade(&events);
    let count = Arc::clone(&reentrant_count);
    let reentrant: EventHandler<_> = Arc::new(move |_| {
        count.fetch_add(1, Ordering::Relaxed);
        if let Some(events) = weak_events.upgrade() {
            events
                .register_event(PacketType::UseCircuitCode, Arc::new(|_| {}), false)
                .unwrap();
        }
    });
    events
        .register_event(PacketType::UseCircuitCode, reentrant, false)
        .unwrap();
    events
        .invoke_raise_event(
            PacketType::UseCircuitCode,
            base_packet(PacketType::UseCircuitCode),
            sim.clone(),
        )
        .unwrap();
    assert_eq!(reentrant_count.load(Ordering::Relaxed), 1);

    let async_events = PacketEventDictionary::new(sim.client.clone()).unwrap();
    let removed_async: EventHandler<_> = Arc::new(|_| {});
    async_events
        .register_event(PacketType::StartPingCheck, Arc::clone(&removed_async), true)
        .unwrap();
    let caller_thread = thread::current().id();
    let (thread_sender, thread_receiver) = channel();
    async_events
        .register_event(
            PacketType::StartPingCheck,
            Arc::new(move |_| thread_sender.send(thread::current().id()).unwrap()),
            false,
        )
        .unwrap();
    async_events
        .unregister_event(PacketType::StartPingCheck, removed_async)
        .unwrap();
    async_events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim,
        )
        .unwrap();
    assert_ne!(
        thread_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        caller_thread,
        "C# keeps a packet type asynchronous after its first async registration"
    );
}

#[test]
fn decoded_packet_callbacks_preserve_optional_raw_datagrams() {
    let client = GridClient::new().expect("client");
    let sim = simulator(&client);
    let events = PacketEventDictionary::new(client).expect("packet events");
    let observed = Arc::new(Mutex::new(None));
    let captured = Arc::clone(&observed);
    events
        .register_event(
            PacketType::Default,
            Arc::new(move |event| *captured.lock().unwrap() = event.raw_data()),
            false,
        )
        .unwrap();
    let raw = vec![0x40, 0, 0, 0, 0, 0, 1, 7, 0, 0, 0, 0];
    events
        .invoke_raise_event_with_raw_data(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim.clone(),
            raw.clone(),
        )
        .unwrap();
    assert_eq!(*observed.lock().unwrap(), Some(raw));

    *observed.lock().unwrap() = Some(vec![1]);
    events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim,
        )
        .unwrap();
    assert_eq!(*observed.lock().unwrap(), None);
}

#[test]
fn synchronous_specific_dispatch_suppresses_default_async_chain_like_reference() {
    let mixed_events = PacketEventDictionary::new(GridClient::new().unwrap()).unwrap();
    let (default_sender, default_receiver) = channel();
    mixed_events
        .register_event(
            PacketType::Default,
            Arc::new(move |_| default_sender.send(()).unwrap()),
            true,
        )
        .unwrap();
    let specific_calls = Arc::new(AtomicUsize::new(0));
    let calls = Arc::clone(&specific_calls);
    mixed_events
        .register_event(
            PacketType::StartPingCheck,
            Arc::new(move |_| {
                calls.fetch_add(1, Ordering::Relaxed);
            }),
            false,
        )
        .unwrap();
    mixed_events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            simulator(&mixed_events.client),
        )
        .unwrap();
    assert_eq!(specific_calls.load(Ordering::Relaxed), 1);
    assert!(
        default_receiver
            .recv_timeout(Duration::from_millis(50))
            .is_err(),
        "C# returns after a synchronous specific chain without queuing the default async chain"
    );
}

#[test]
fn abort_and_callback_unregistration_preserve_reference_noop_behavior() {
    let client = GridClient::new().unwrap();
    let manager = client.network();
    let progress = Arc::new(Mutex::new(Vec::new()));
    let observed = Arc::clone(&progress);
    let _guard = manager.subscribe_login_progress(Arc::new(move |args| {
        observed.lock().unwrap().push(args);
    }));
    let callback = NetworkManagerLoginResponseCallback::from_handler(|_, _, _, _, _| {});
    manager
        .register_login_response_callback_with_login_response_callback(callback.clone())
        .unwrap();
    manager
        .unregister_login_response_callback(callback.clone())
        .unwrap();
    manager
        .unregister_login_response_callback(callback)
        .unwrap();

    manager.abort_login().unwrap();
    assert_eq!(manager.login_status_code(), LoginStatus::Failed);
    assert_eq!(manager.login_message(), "Abort Requested");
    let progress = progress.lock().unwrap();
    assert_eq!(progress.len(), 1);
    assert_eq!(progress[0].status(), LoginStatus::Failed);
    assert_eq!(progress[0].message(), "Abort Requested");
}

struct TestCapsMessage;
impl IMessage for TestCapsMessage {
    fn deserialize(&mut self, _map: OSDMap) -> Result<(), Error> {
        Ok(())
    }

    fn serialize(&self) -> Result<OSDMap, Error> {
        OSDMap::new_with_constructor()
    }
}

#[test]
fn caps_callbacks_are_ordered_and_subscription_guards_unregister() {
    let client = GridClient::new().expect("client");
    let sim = simulator(&client);
    let events = CapsEventDictionary::new(client).expect("CAPS events");
    let order = Arc::new(Mutex::new(Vec::new()));

    let default_order = Arc::clone(&order);
    let default = CapsEventQueueCallback::from_handler(move |_, _, _| {
        default_order.lock().unwrap().push("default");
    });
    let specific_order = Arc::clone(&order);
    let specific = CapsEventQueueCallback::from_handler(move |_, _, _| {
        specific_order.lock().unwrap().push("specific");
    });
    let default_guard = events.subscribe(String::new(), default);
    let specific_guard = events.subscribe("EnableSimulator".into(), specific);

    events.invoke_raise_event("EnableSimulator", &TestCapsMessage, sim.clone());
    assert_eq!(*order.lock().unwrap(), ["default", "specific"]);

    drop(specific_guard);
    order.lock().unwrap().clear();
    events.invoke_raise_event("EnableSimulator", &TestCapsMessage, sim);
    assert_eq!(*order.lock().unwrap(), ["default"]);
    drop(default_guard);
}

#[test]
fn concurrent_registration_and_removal_is_deterministic() {
    const CALLBACKS: usize = 16;
    let client = GridClient::new().expect("client");
    let sim = simulator(&client);
    let events = Arc::new(PacketEventDictionary::new(client).unwrap());
    let barrier = Arc::new(Barrier::new(CALLBACKS + 1));
    let calls = Arc::new(AtomicUsize::new(0));
    let (guard_sender, guard_receiver) = channel();

    thread::scope(|scope| {
        for _ in 0..CALLBACKS {
            let events = Arc::clone(&events);
            let barrier = Arc::clone(&barrier);
            let calls = Arc::clone(&calls);
            let guard_sender = guard_sender.clone();
            scope.spawn(move || {
                let guard = events.subscribe(
                    PacketType::StartPingCheck,
                    Arc::new(move |_| {
                        calls.fetch_add(1, Ordering::Relaxed);
                    }),
                    false,
                );
                guard_sender.send(guard).unwrap();
                barrier.wait();
            });
        }
        barrier.wait();
    });
    drop(guard_sender);
    let guards: Vec<_> = guard_receiver.into_iter().collect();

    events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim.clone(),
        )
        .unwrap();
    assert_eq!(calls.load(Ordering::Relaxed), CALLBACKS);

    drop(guards);
    calls.store(0, Ordering::Relaxed);
    events
        .invoke_raise_event(
            PacketType::StartPingCheck,
            base_packet(PacketType::StartPingCheck),
            sim,
        )
        .unwrap();
    assert_eq!(calls.load(Ordering::Relaxed), 0);
}

#[test]
fn sim_connecting_can_cancel_without_leaking_collection_state() {
    let client = GridClient::new().expect("client");
    let manager = NetworkManager::new(client).expect("manager");
    let guard = manager.subscribe_sim_connecting(Arc::new(|mut args| args.set_cancel(true)));

    let connected = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            loopback(9),
            0x2000,
            true,
            Some(Uri("http://caps.invalid/seed".into())),
            512,
            256,
        )
        .unwrap();

    assert!(connected.is_none());
    assert!(manager.simulators.is_empty());
    assert!(
        manager.connected(),
        "C# marks the manager connected before raising the cancellable event"
    );
    assert!(manager.current_sim().is_none());
    drop(guard);
}

enum ServerCommand {
    Ping,
    Stats,
    Disable,
    ExpectEconomy,
    Handoff,
    ExpectThrottle,
    ExpectLogout,
    LogoutReply(Vec<UUID>),
    Stop,
}

#[derive(Debug, Eq, PartialEq)]
enum ServerReport {
    Ack(u32),
    Circuit(u32),
    HandshakeReply(u32),
    Economy,
    Logout(UUID, UUID),
    Movement(u32),
    Ping(u8),
    Throttle(u32, Vec<u8>),
}

struct FakeServer {
    endpoint: SocketAddr,
    commands: Sender<ServerCommand>,
    reports: Receiver<ServerReport>,
    handle: Option<JoinHandle<()>>,
}

impl Drop for FakeServer {
    fn drop(&mut self) {
        let _ = self.commands.send(ServerCommand::Stop);
        if let Some(handle) = self.handle.take() {
            let _ = handle.join();
        }
    }
}

fn packet_type(bytes: &[u8]) -> Option<PacketType> {
    let mut packet_end = i32::try_from(bytes.len()).ok()?.checked_sub(1)?;
    Packet::build_packet_with_bytes_int32_bytes(bytes.to_vec(), &mut packet_end, vec![0; 8192])
        .ok()
        .map(|packet| packet.type_)
}

fn ack(sequence: u32) -> Vec<u8> {
    let mut packet = PacketAckPacket::new_with_constructor().unwrap();
    packet.packets = vec![PacketAckPacketPacketsBlock { id: sequence }];
    let mut bytes = packet.to_bytes_with_method().unwrap();
    bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
    bytes
}

fn serve_ping_command(
    socket: &UdpSocket,
    client_endpoint: SocketAddr,
    report_sender: &Sender<ServerReport>,
    buffer: &mut [u8],
) {
    let mut ping = StartPingCheckPacket::new_with_constructor().unwrap();
    ping.ping_id.ping_id = 42;
    ping.ping_id.oldest_unacked = 1;
    let mut bytes = ping.to_bytes_with_method().unwrap();
    bytes[0] &= !Helpers::MSG_ZEROCODED;
    bytes[0] |= Helpers::MSG_RELIABLE;
    bytes[1..5].copy_from_slice(&99_u32.to_be_bytes());
    socket.send_to(&bytes, client_endpoint).unwrap();

    loop {
        let (length, _) = socket.recv_from(buffer).expect("ping response");
        match packet_type(&buffer[..length]) {
            Some(PacketType::PacketAck) => {
                let mut position = 0;
                let response =
                    PacketAckPacket::new_with_bytes_int32(buffer[..length].to_vec(), &mut position)
                        .unwrap();
                assert_eq!(response.packets.len(), 1);
                report_sender
                    .send(ServerReport::Ack(response.packets[0].id))
                    .unwrap();
            }
            Some(PacketType::CompletePingCheck) => {
                let mut position = 0;
                let response =
                    libremetaverse::packets::CompletePingCheckPacket::new_with_bytes_int32(
                        buffer[..length].to_vec(),
                        &mut position,
                    )
                    .unwrap();
                report_sender
                    .send(ServerReport::Ping(response.ping_id.ping_id))
                    .unwrap();
                break;
            }
            _ => {}
        }
    }
}

fn spawn_fake_server() -> FakeServer {
    let socket = UdpSocket::bind(loopback(0)).expect("fake UDP server");
    socket
        .set_read_timeout(Some(Duration::from_secs(2)))
        .unwrap();
    let endpoint = socket.local_addr().unwrap();
    let (command_sender, command_receiver) = channel();
    let (report_sender, report_receiver) = channel();
    let handle = thread::spawn(move || {
        let mut buffer = [0_u8; 4096];
        let (length, client_endpoint) = socket.recv_from(&mut buffer).expect("UseCircuitCode");
        assert_eq!(
            packet_type(&buffer[..length]),
            Some(PacketType::UseCircuitCode)
        );
        let mut position = 0;
        let use_circuit =
            UseCircuitCodePacket::new_with_bytes_int32(buffer[..length].to_vec(), &mut position)
                .expect("decode UseCircuitCode");
        report_sender
            .send(ServerReport::Circuit(use_circuit.circuit_code.code))
            .unwrap();
        let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap());
        socket.send_to(&ack(sequence), client_endpoint).unwrap();

        let mut handshake = RegionHandshakePacket::new_with_constructor().unwrap();
        handshake.region_info.sim_name = b"Fake Region\0".to_vec();
        handshake.region_info.water_height = 21.5;
        handshake.region_info2.region_id = UUID::new_with_u_int64(99).unwrap();
        let raw = handshake.to_bytes_with_method().unwrap();
        let mut bytes = vec![0_u8; raw.len().saturating_mul(2).saturating_add(2)];
        let length = Helpers::zero_encode(
            Some(&raw),
            i32::try_from(raw.len()).unwrap(),
            Some(&mut bytes),
        )
        .unwrap();
        bytes.truncate(usize::try_from(length).unwrap());
        socket.send_to(&bytes, client_endpoint).unwrap();

        loop {
            let (length, _) = socket.recv_from(&mut buffer).expect("handshake reply");
            let received_type = packet_type(&buffer[..length]);
            if received_type == Some(PacketType::RegionHandshakeReply) {
                let mut position = 0;
                let mut packet_end = i32::try_from(length).unwrap() - 1;
                let mut zero_buffer = vec![0_u8; 8192];
                let mut response = RegionHandshakeReplyPacket::new_with_constructor().unwrap();
                response
                    .from_bytes_with_bytes_int32_int32_bytes(
                        buffer[..length].to_vec(),
                        &mut position,
                        &mut packet_end,
                        Some(&mut zero_buffer),
                    )
                    .unwrap();
                report_sender
                    .send(ServerReport::HandshakeReply(response.region_info.flags))
                    .unwrap();
                break;
            }
        }

        while let Ok(command) = command_receiver.recv() {
            match command {
                ServerCommand::Ping => {
                    serve_ping_command(&socket, client_endpoint, &report_sender, &mut buffer);
                }
                ServerCommand::Stats => {
                    let mut stats = SimStatsPacket::new_with_constructor().unwrap();
                    stats.stat = vec![
                        SimStatsPacketStatBlock {
                            stat_id: 0,
                            stat_value: 0.75,
                        },
                        SimStatsPacketStatBlock {
                            stat_id: 1,
                            stat_value: 44.5,
                        },
                        SimStatsPacketStatBlock {
                            stat_id: 11,
                            stat_value: 321.0,
                        },
                    ];
                    let mut bytes = stats.to_bytes_with_method().unwrap();
                    bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
                    socket.send_to(&bytes, client_endpoint).unwrap();
                }
                ServerCommand::Disable => {
                    let disable = DisableSimulatorPacket::new_with_constructor().unwrap();
                    let mut bytes = disable.to_bytes_with_method().unwrap();
                    bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
                    socket.send_to(&bytes, client_endpoint).unwrap();
                }
                ServerCommand::ExpectEconomy => loop {
                    let (length, _) = socket.recv_from(&mut buffer).expect("EconomyDataRequest");
                    if packet_type(&buffer[..length]) == Some(PacketType::EconomyDataRequest) {
                        report_sender.send(ServerReport::Economy).unwrap();
                        break;
                    }
                },
                ServerCommand::Handoff => {
                    loop {
                        let (length, _) = socket.recv_from(&mut buffer).expect("UseCircuitCode");
                        if packet_type(&buffer[..length]) != Some(PacketType::UseCircuitCode) {
                            continue;
                        }
                        let mut position = 0;
                        let use_circuit = UseCircuitCodePacket::new_with_bytes_int32(
                            buffer[..length].to_vec(),
                            &mut position,
                        )
                        .unwrap();
                        report_sender
                            .send(ServerReport::Circuit(use_circuit.circuit_code.code))
                            .unwrap();
                        let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap());
                        socket.send_to(&ack(sequence), client_endpoint).unwrap();
                        break;
                    }
                    loop {
                        let (length, _) = socket
                            .recv_from(&mut buffer)
                            .expect("CompleteAgentMovement");
                        if packet_type(&buffer[..length]) != Some(PacketType::CompleteAgentMovement)
                        {
                            continue;
                        }
                        let mut position = 0;
                        let movement = CompleteAgentMovementPacket::new_with_bytes_int32(
                            buffer[..length].to_vec(),
                            &mut position,
                        )
                        .unwrap();
                        report_sender
                            .send(ServerReport::Movement(movement.agent_data.circuit_code))
                            .unwrap();
                        break;
                    }
                }
                ServerCommand::ExpectThrottle => loop {
                    let (length, _) = socket.recv_from(&mut buffer).expect("AgentThrottle");
                    if packet_type(&buffer[..length]) != Some(PacketType::AgentThrottle) {
                        continue;
                    }
                    let mut bytes = buffer[..length].to_vec();
                    if bytes[0] & Helpers::MSG_ZEROCODED != 0 {
                        let mut decoded = vec![0; 8192];
                        let decoded_length = Helpers::zero_decode(
                            Some(&bytes),
                            i32::try_from(bytes.len()).unwrap(),
                            Some(&mut decoded),
                        )
                        .unwrap();
                        decoded.truncate(usize::try_from(decoded_length).unwrap());
                        decoded[0] &= !Helpers::MSG_ZEROCODED;
                        bytes = decoded;
                    }
                    let mut position = 0;
                    let packet =
                        AgentThrottlePacket::new_with_bytes_int32(bytes, &mut position).unwrap();
                    report_sender
                        .send(ServerReport::Throttle(
                            packet.agent_data.circuit_code,
                            packet.throttle.throttles,
                        ))
                        .unwrap();
                    break;
                },
                ServerCommand::ExpectLogout => loop {
                    let (length, _) = socket.recv_from(&mut buffer).expect("LogoutRequest");
                    if packet_type(&buffer[..length]) != Some(PacketType::LogoutRequest) {
                        continue;
                    }
                    let mut position = 0;
                    let request = LogoutRequestPacket::new_with_bytes_int32(
                        buffer[..length].to_vec(),
                        &mut position,
                    )
                    .unwrap();
                    report_sender
                        .send(ServerReport::Logout(
                            request.agent_data.agent_id,
                            request.agent_data.session_id,
                        ))
                        .unwrap();
                    break;
                },
                ServerCommand::LogoutReply(inventory_items) => loop {
                    let (length, _) = socket.recv_from(&mut buffer).expect("LogoutRequest");
                    if packet_type(&buffer[..length]) != Some(PacketType::LogoutRequest) {
                        continue;
                    }
                    let mut position = 0;
                    let request = LogoutRequestPacket::new_with_bytes_int32(
                        buffer[..length].to_vec(),
                        &mut position,
                    )
                    .unwrap();
                    report_sender
                        .send(ServerReport::Logout(
                            request.agent_data.agent_id,
                            request.agent_data.session_id,
                        ))
                        .unwrap();
                    let mut reply = LogoutReplyPacket::new_with_constructor().unwrap();
                    reply.agent_data.agent_id = request.agent_data.agent_id;
                    reply.agent_data.session_id = request.agent_data.session_id;
                    reply.inventory_data = inventory_items
                        .into_iter()
                        .map(|item_id| LogoutReplyPacketInventoryDataBlock { item_id })
                        .collect();
                    let mut bytes = reply.to_bytes_with_method().unwrap();
                    bytes[0] &= !(Helpers::MSG_RELIABLE | Helpers::MSG_ZEROCODED);
                    socket.send_to(&bytes, client_endpoint).unwrap();
                    break;
                },
                ServerCommand::Stop => break,
            }
        }
    });
    FakeServer {
        endpoint,
        commands: command_sender,
        reports: report_receiver,
        handle: Some(handle),
    }
}

fn spawn_ack_only_server() -> (SocketAddr, JoinHandle<()>) {
    let socket = UdpSocket::bind(loopback(0)).expect("ACK-only UDP server");
    socket
        .set_read_timeout(Some(Duration::from_secs(2)))
        .unwrap();
    let endpoint = socket.local_addr().unwrap();
    let handle = thread::spawn(move || {
        let mut buffer = [0_u8; 4096];
        let (length, client_endpoint) = socket.recv_from(&mut buffer).expect("UseCircuitCode");
        assert_eq!(
            packet_type(&buffer[..length]),
            Some(PacketType::UseCircuitCode)
        );
        let sequence = u32::from_be_bytes(buffer[1..5].try_into().unwrap());
        socket.send_to(&ack(sequence), client_endpoint).unwrap();
    });
    (endpoint, handle)
}

fn assert_ping_exchange(server: &FakeServer, packet_receiver: &Receiver<PacketType>) {
    server.commands.send(ServerCommand::Ping).unwrap();
    assert_eq!(
        packet_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        PacketType::StartPingCheck
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Ack(99)
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Ping(42)
    );
}

fn assert_reuse_preserves_connected_override(
    manager: &mut NetworkManager,
    server: &FakeServer,
    simulator: &Simulator,
) {
    manager.set_connected(false);
    let reused = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            simulator.handle,
            false,
            None,
            simulator.size_x,
            simulator.size_y,
        )
        .unwrap()
        .unwrap();
    assert_eq!(reused, simulator.clone());
    assert!(
        !manager.connected(),
        "C# does not alter manager Connected when reusing a connected simulator"
    );
    manager.set_connected(true);
}

#[test]
fn grid_client_throttle_sender_writes_agent_throttle_to_the_simulator() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client.clone()).expect("manager");
    manager.set_circuit_code(0x4455_6677);
    let simulator = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            0x1234,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .expect("simulator");
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Circuit(0x4455_6677)
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );

    let throttle = AgentThrottle::new_with_grid_client(client).unwrap();
    server.commands.send(ServerCommand::ExpectThrottle).unwrap();
    throttle.set_with_simulator(Some(simulator)).unwrap();
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Throttle(0x4455_6677, throttle.to_bytes().unwrap())
    );

    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[test]
fn fake_server_drives_circuit_handshake_ping_disable_and_disconnect_reasons() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(0x1122_3344);
    let (changed_sender, changed_receiver) = channel();
    let (connected_sender, connected_receiver) = channel();
    let (packet_sender, packet_receiver) = channel();
    let (sim_disconnected_sender, sim_disconnected_receiver) = channel();
    let (disconnected_sender, disconnected_receiver) = channel();
    let guards = [
        manager.subscribe_sim_changed(Arc::new(move |args| {
            changed_sender
                .send(args.previous_simulator().is_none())
                .unwrap();
        })),
        manager.subscribe_sim_connected(Arc::new(move |args| {
            connected_sender.send(args.simulator().handle).unwrap();
        })),
        manager.subscribe_sim_disconnected(Arc::new(move |args| {
            sim_disconnected_sender
                .send((args.reason(), args.was_connected()))
                .unwrap();
        })),
        manager.subscribe_disconnected(Arc::new(move |args| {
            disconnected_sender.send(args.reason()).unwrap();
        })),
    ];
    manager
        .register_callback_with_packet_type_event_handler_boolean(
            PacketType::StartPingCheck,
            Arc::new(move |args| packet_sender.send(args.packet().type_).unwrap()),
            false,
        )
        .unwrap();

    let seed = Uri("http://caps.invalid/seed".into());
    let sim = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            0x1234_0000,
            true,
            Some(seed.clone()),
            512,
            256,
        )
        .unwrap()
        .expect("connected simulator");

    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Circuit(0x1122_3344)
    );
    assert!(
        changed_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap()
    );
    assert_eq!(
        connected_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        sim.handle
    );
    assert_eq!(manager.current_sim(), Some(sim.clone()));
    assert_eq!(sim.seed_capability(), Some(seed));
    assert_eq!(sim.size_x, 512);
    assert_eq!(sim.size_y, 256);
    assert_eq!(sim.name, "Fake Region");
    assert_eq!(sim.region_id, UUID::new_with_u_int64(99).unwrap());
    assert_eq!(sim.water_height, 21.5);
    wait_until(|| sim.handshake_complete());
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    assert_reuse_preserves_connected_override(&mut manager, &server, &sim);

    assert_ping_exchange(&server, &packet_receiver);
    server.commands.send(ServerCommand::Stats).unwrap();
    wait_until(|| sim.stats.objects() == 321);
    assert_eq!(sim.stats.dilation(), 0.75);
    assert_eq!(sim.stats.fps(), 44);
    assert!(sim.stats.get_recv_packets().unwrap() >= 3);
    assert!(sim.stats.get_recv_bytes().unwrap() > 0);
    assert!(sim.stats.get_sent_packets().unwrap() >= 3);
    assert!(sim.stats.get_sent_bytes().unwrap() > 0);
    assert_eq!(sim.stats.get_dropped_packets().unwrap(), 0);

    server.commands.send(ServerCommand::Disable).unwrap();
    assert_eq!(
        sim_disconnected_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        (NetworkManagerDisconnectType::NetworkTimeout, true)
    );
    assert_eq!(
        disconnected_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        NetworkManagerDisconnectType::SimShutdown
    );
    assert!(manager.simulators.is_empty());
    assert!(manager.current_sim().is_none());
    assert!(!manager.connected());
    drop(guards);
}

#[test]
fn concurrent_disconnect_emits_each_transition_once() {
    let _network_guard = network_test_guard();
    const CALLERS: usize = 12;
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(7);
    let sim_disconnects = Arc::new(AtomicUsize::new(0));
    let global_disconnects = Arc::new(AtomicUsize::new(0));
    let sim_count = Arc::clone(&sim_disconnects);
    let sim_guard = manager.subscribe_sim_disconnected(Arc::new(move |args| {
        assert!(args.was_connected());
        sim_count.fetch_add(1, Ordering::Relaxed);
    }));
    let global_count = Arc::clone(&global_disconnects);
    let global_guard = manager.subscribe_disconnected(Arc::new(move |_| {
        global_count.fetch_add(1, Ordering::Relaxed);
    }));
    let sim = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            9,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let manager = Arc::new(manager);
    let barrier = Arc::new(Barrier::new(CALLERS));

    thread::scope(|scope| {
        for _ in 0..CALLERS {
            let manager = Arc::clone(&manager);
            let simulator = sim.clone();
            let barrier = Arc::clone(&barrier);
            scope.spawn(move || {
                barrier.wait();
                manager.disconnect_sim(simulator, false).unwrap();
            });
        }
    });

    assert_eq!(sim_disconnects.load(Ordering::Relaxed), 1);
    assert_eq!(global_disconnects.load(Ordering::Relaxed), 1);
    assert!(manager.simulators.is_empty());
    assert!(!manager.connected());
    drop(sim_guard);
    drop(global_guard);
}

#[test]
fn sim_disconnected_callback_can_reenter_disconnect_without_deadlock() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(8);
    let simulator = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            10,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let manager = Arc::new(manager);
    let weak_manager = Arc::downgrade(&manager);
    let callback_completed = Arc::new(AtomicBool::new(false));
    let completed = Arc::clone(&callback_completed);
    let guard = manager.subscribe_sim_disconnected(Arc::new(move |args| {
        let manager = weak_manager.upgrade().expect("manager during callback");
        manager
            .disconnect_sim(args.simulator(), false)
            .expect("reentrant disconnect");
        completed.store(true, Ordering::Release);
    }));

    manager.disconnect_sim(simulator, false).unwrap();

    assert!(callback_completed.load(Ordering::Acquire));
    assert!(manager.simulators.is_empty());
    assert!(!manager.connected());
    drop(guard);
}

#[test]
fn keepalive_requires_two_silent_intervals_before_network_timeout() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(11);
    let (disconnected_sender, disconnected_receiver) = channel();
    let guard = manager.subscribe_disconnected(Arc::new(move |args| {
        disconnected_sender.send(args.reason()).unwrap();
    }));
    let sim = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            12,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    wait_until(|| sim.handshake_complete());

    manager.poll_keepalive();
    assert!(manager.connected());
    assert!(
        disconnected_receiver
            .recv_timeout(Duration::from_millis(50))
            .is_err()
    );

    manager.poll_keepalive();
    assert_eq!(
        disconnected_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        NetworkManagerDisconnectType::NetworkTimeout
    );
    assert!(!manager.connected());
    assert!(manager.current_sim().is_none());
    drop(guard);
}

#[test]
fn enable_simulator_caps_event_adds_each_new_region_once() {
    let _network_guard = network_test_guard();
    let first_server = spawn_fake_server();
    let second_server = spawn_fake_server();
    let mut client = GridClient::new().expect("client");
    client.settings().agent_settings_mut().multiple_sims = true;
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(17);
    let current = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            first_server.endpoint,
            18,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();

    let mut message = EnableSimulatorMessage::new().unwrap();
    let mut info = EnableSimulatorMessageSimulatorInfoBlock::new().unwrap();
    info.ip = second_server.endpoint.ip();
    info.port = i32::from(second_server.endpoint.port());
    info.region_handle = 19;
    info.region_size_x = 1024;
    info.region_size_y = 512;
    message.simulators = vec![info];

    manager.dispatch_caps_event("EnableSimulator", &message, current.clone());
    manager.dispatch_caps_event("EnableSimulator", &message, current);

    assert_eq!(manager.simulators.len(), 2);
    let added = manager
        .find_simulator_with_ip_end_point(second_server.endpoint)
        .unwrap()
        .expect("enabled simulator");
    assert_eq!(added.handle, 19);
    assert_eq!(added.size_x, 1024);
    assert_eq!(added.size_y, 512);
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(17)
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );

    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[test]
fn connected_region_handoff_preserves_old_sim_and_sends_complete_agent_movement() {
    let _network_guard = network_test_guard();
    let first_server = spawn_fake_server();
    let second_server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(0x7788);
    let first = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            first_server.endpoint,
            100,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let second = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            second_server.endpoint,
            200,
            false,
            None,
            512,
            256,
        )
        .unwrap()
        .unwrap();
    for server in [&first_server, &second_server] {
        assert_eq!(
            server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
            ServerReport::Circuit(0x7788)
        );
        assert_eq!(
            server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
            ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
        );
    }

    let (changed_sender, changed_receiver) = channel();
    let _changed = manager.subscribe_sim_changed(Arc::new(move |args| {
        changed_sender.send(args.previous_simulator()).unwrap();
    }));
    second_server.commands.send(ServerCommand::Handoff).unwrap();
    let selected = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            second_server.endpoint,
            200,
            true,
            None,
            512,
            256,
        )
        .unwrap()
        .unwrap();

    assert_eq!(selected, second);
    assert_eq!(manager.current_sim(), Some(second.clone()));
    assert_eq!(
        changed_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        Some(first.clone())
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(0x7788)
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Movement(0x7788)
    );
    assert!(first.connected());
    assert!(second.connected());
    assert_eq!(manager.simulators.len(), 2);

    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[test]
fn manager_reconnects_with_fresh_workers_after_complete_shutdown() {
    let _network_guard = network_test_guard();
    let first_server = spawn_fake_server();
    let second_server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(0x7878);

    manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            first_server.endpoint,
            250,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    assert_eq!(
        first_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(0x7878)
    );
    assert_eq!(
        first_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
    assert!(!manager.connected());
    assert!(manager.current_sim().is_none());
    assert!(manager.simulators.is_empty());

    manager.set_circuit_code(0x7979);
    let reconnected = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            second_server.endpoint,
            251,
            true,
            None,
            512,
            256,
        )
        .unwrap()
        .unwrap();
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(0x7979)
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    assert!(manager.connected());
    assert_eq!(manager.current_sim(), Some(reconnected));
    assert_eq!(manager.simulators.len(), 1);

    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[test]
fn logout_reply_orders_events_returns_inventory_and_tears_down_once() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(0x8899);
    manager.set_login_seed_capability(Some(Uri("https://caps.invalid/secret".into())));
    let login_response = manager
        .login_response_data
        .as_mut()
        .expect("login response storage");
    login_response.set_session_id(
        UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
    );
    login_response.set_secure_session_id(
        UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
    );
    login_response.set_mfa_hash("secret-mfa".into());
    login_response.set_seed_capability("https://caps.invalid/secret".into());
    login_response.set_next_url("https://login.invalid/secret-token".into());
    manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            300,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Circuit(0x8899)
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );

    let order = Arc::new(Mutex::new(Vec::new()));
    let logged_order = Arc::clone(&order);
    let (inventory_sender, inventory_receiver) = channel();
    let _logged_out = manager.subscribe_logged_out(Arc::new(move |args| {
        logged_order.lock().unwrap().push("logged_out");
        inventory_sender.send(args.inventory_items).unwrap();
    }));
    let sim_order = Arc::clone(&order);
    let _sim_disconnected = manager.subscribe_sim_disconnected(Arc::new(move |_| {
        sim_order.lock().unwrap().push("sim_disconnected");
    }));
    let disconnected_order = Arc::clone(&order);
    let disconnected_count = Arc::new(AtomicUsize::new(0));
    let count = Arc::clone(&disconnected_count);
    let _disconnected = manager.subscribe_disconnected(Arc::new(move |_| {
        count.fetch_add(1, Ordering::Relaxed);
        disconnected_order.lock().unwrap().push("disconnected");
    }));
    let inventory = vec![
        UUID::new_with_string("11111111-1111-1111-1111-111111111111".into()).unwrap(),
        UUID::new_with_string("22222222-2222-2222-2222-222222222222".into()).unwrap(),
    ];
    server
        .commands
        .send(ServerCommand::LogoutReply(inventory.clone()))
        .unwrap();

    manager.logout_with_method().unwrap();

    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Logout(UUID::zero(), UUID::zero())
    );
    assert_eq!(
        inventory_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        inventory
    );
    assert_eq!(
        *order.lock().unwrap(),
        ["logged_out", "sim_disconnected", "disconnected"]
    );
    assert!(!manager.connected());
    assert!(manager.current_sim().is_none());
    assert!(manager.simulators.is_empty());
    assert_eq!(manager.circuit_code(), 0);
    assert!(manager.login_seed_capability().is_none());
    let login_response = manager
        .login_response_data
        .as_ref()
        .expect("login response storage");
    assert_eq!(login_response.session_id(), UUID::zero());
    assert_eq!(login_response.secure_session_id(), UUID::zero());
    assert!(login_response.mfa_hash().is_empty());
    assert!(login_response.seed_capability().is_empty());
    assert!(login_response.next_url().is_empty());

    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
    assert_eq!(disconnected_count.load(Ordering::Relaxed), 1);
}

#[test]
fn async_logout_timeout_and_cancellation_have_distinct_reference_results() {
    let _network_guard = network_test_guard();
    let timeout_server = spawn_fake_server();
    let mut timeout_client = GridClient::new().expect("client");
    timeout_client.settings().timing().logout_timeout = 25;
    let mut timeout_manager = NetworkManager::new(timeout_client).expect("manager");
    timeout_manager.set_circuit_code(0x9911);
    timeout_manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            timeout_server.endpoint,
            400,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let _ = timeout_server
        .reports
        .recv_timeout(Duration::from_secs(1))
        .unwrap();
    let _ = timeout_server
        .reports
        .recv_timeout(Duration::from_secs(1))
        .unwrap();
    let (reason_sender, reason_receiver) = channel();
    let _disconnected = timeout_manager.subscribe_disconnected(Arc::new(move |args| {
        reason_sender.send(args.reason()).unwrap();
    }));

    block_on(timeout_manager.logout_with_cancellation_token(None)).unwrap();

    assert_eq!(
        reason_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        NetworkManagerDisconnectType::NetworkTimeout
    );
    assert!(!timeout_manager.connected());

    let cancel_server = spawn_fake_server();
    let mut cancel_client = GridClient::new().expect("client");
    cancel_client.settings().timing().logout_timeout = 1_000;
    let mut cancel_manager = NetworkManager::new(cancel_client).expect("manager");
    cancel_manager.set_circuit_code(0x9922);
    cancel_manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            cancel_server.endpoint,
            500,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let _ = cancel_server
        .reports
        .recv_timeout(Duration::from_secs(1))
        .unwrap();
    let _ = cancel_server
        .reports
        .recv_timeout(Duration::from_secs(1))
        .unwrap();
    let cancellation = CancellationTokenSource::new();
    cancellation.cancel();

    let result =
        block_on(cancel_manager.logout_with_cancellation_token(Some(cancellation.token())));

    assert_eq!(result, Err(libremetaverse::Error::Cancelled));
    assert!(cancel_manager.connected());
    cancel_manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[test]
fn begin_logout_times_out_nonblocking_and_emits_logged_out_after_teardown() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let mut client = GridClient::new().expect("client");
    client.settings().timing().logout_timeout = 250;
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(0x9933);
    manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            600,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let _ = server.reports.recv_timeout(Duration::from_secs(1)).unwrap();
    let _ = server.reports.recv_timeout(Duration::from_secs(1)).unwrap();
    let order = Arc::new(Mutex::new(Vec::new()));
    let disconnected_order = Arc::clone(&order);
    let _disconnected = manager.subscribe_disconnected(Arc::new(move |_| {
        disconnected_order.lock().unwrap().push("disconnected");
    }));
    let logged_order = Arc::clone(&order);
    let (logged_sender, logged_receiver) = channel();
    let _logged = manager.subscribe_logged_out(Arc::new(move |args| {
        logged_order.lock().unwrap().push("logged_out");
        logged_sender.send(args.inventory_items).unwrap();
    }));

    let started = Instant::now();
    manager.begin_logout().unwrap();
    assert!(started.elapsed() < Duration::from_millis(100));
    assert_eq!(
        logged_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        Vec::<UUID>::new()
    );
    assert_eq!(*order.lock().unwrap(), ["disconnected", "logged_out"]);
    assert!(!manager.connected());
    assert!(manager.simulators.is_empty());
}

#[test]
fn explicit_shutdown_cancels_a_pending_begin_logout_without_retaining_client() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let mut client = GridClient::new().expect("client");
    client.settings().timing().logout_timeout = 5_000;
    let mut manager = NetworkManager::new(client.clone()).expect("manager");
    manager.set_circuit_code(0x9944);
    manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            server.endpoint,
            700,
            true,
            None,
            256,
            256,
        )
        .unwrap()
        .unwrap();
    let _ = server.reports.recv_timeout(Duration::from_secs(1)).unwrap();
    let _ = server.reports.recv_timeout(Duration::from_secs(1)).unwrap();

    server.commands.send(ServerCommand::ExpectLogout).unwrap();
    manager.begin_logout().unwrap();
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Logout(UUID::zero(), UUID::zero())
    );
    assert_eq!(manager.pending_logout_tasks(), 1);
    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
    wait_until(|| manager.pending_logout_tasks() == 0);
    assert_eq!(
        manager.last_logout_error(),
        Some(libremetaverse::Error::Cancelled)
    );
    assert!(!manager.connected());
    assert!(manager.simulators.is_empty());
}

#[test]
fn async_connect_and_shutdown_do_not_require_an_ambient_tokio_runtime() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let client = GridClient::new().expect("client");
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(23);

    let simulator = block_on(
        manager.connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32_20e51cc0(
            server.endpoint,
            24,
            true,
            None,
            256,
            256,
        ),
    )
    .unwrap()
    .expect("async connection");

    assert!(simulator.connected());
    assert_eq!(manager.current_sim(), Some(simulator));
    block_on(manager.shutdown_with_disconnect_type_string_7641243c(
        NetworkManagerDisconnectType::ClientInitiated,
        "ClientInitiated".into(),
    ))
    .unwrap();
    assert!(!manager.connected());
}

#[test]
fn handshake_timeout_preserves_reference_connect_result_and_shutdown_tears_down_current_sim() {
    let _network_guard = network_test_guard();
    let (endpoint, server) = spawn_ack_only_server();
    let mut client = GridClient::new().expect("client");
    client.settings().timing().login_timeout = 25;
    let mut manager = NetworkManager::new(client).expect("manager");
    manager.set_circuit_code(25);
    let sim_disconnects = Arc::new(AtomicUsize::new(0));
    let disconnect_count = Arc::clone(&sim_disconnects);
    let guard = manager.subscribe_sim_disconnected(Arc::new(move |_| {
        disconnect_count.fetch_add(1, Ordering::Relaxed);
    }));

    let simulator = manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            endpoint, 26, true, None, 256, 256,
        )
        .unwrap()
        .expect("C# returns the UDP-connected simulator after handshake timeout");
    server.join().unwrap();

    assert!(!simulator.handshake_complete());
    assert!(manager.simulators.is_empty());
    assert_eq!(manager.current_sim(), Some(simulator.clone()));
    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
    assert!(!simulator.connected());
    assert_eq!(sim_disconnects.load(Ordering::Relaxed), 1);
    drop(guard);
}

fn login_http_response(body: OSD) -> HttpResponse {
    let body = OSDParser::serialize_llsd_xml_bytes(body).expect("serialize LLSD");
    HttpResponse {
        status_code: 200,
        headers: BTreeMap::from([("Content-Length".into(), body.len().to_string())]),
        content_type: Some("application/llsd+xml".into()),
        body,
    }
}

fn successful_login_response(endpoint: SocketAddr, seed: &str) -> OSD {
    OSD::Map(HashMap::from([
        ("login".into(), OSD::Boolean(true)),
        ("message".into(), OSD::String("Welcome".into())),
        (
            "agent_id".into(),
            OSD::UUID(
                UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
            ),
        ),
        (
            "session_id".into(),
            OSD::UUID(
                UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
            ),
        ),
        (
            "secure_session_id".into(),
            OSD::UUID(
                UUID::new_with_string("cccccccc-cccc-cccc-cccc-cccccccccccc".into()).unwrap(),
            ),
        ),
        ("circuit_code".into(), OSD::Integer(0x1234)),
        ("region_x".into(), OSD::Integer(1000)),
        ("region_y".into(), OSD::Integer(2000)),
        ("region_size_x".into(), OSD::Integer(256)),
        ("region_size_y".into(), OSD::Integer(256)),
        ("sim_ip".into(), OSD::String(endpoint.ip().to_string())),
        ("sim_port".into(), OSD::Integer(i32::from(endpoint.port()))),
        ("seed_capability".into(), OSD::String(seed.into())),
        (
            "account_level_benefits".into(),
            OSD::Map(HashMap::from([(
                "group_membership_limit".into(),
                OSD::Integer(70),
            )])),
        ),
    ]))
}

#[tokio::test(flavor = "current_thread")]
async fn login_hashes_credentials_parses_response_and_hands_off_session_to_udp_and_caps() {
    let _network_guard = network_test_guard();
    let server = spawn_fake_server();
    let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
    let recorded = Arc::clone(&requests);
    let endpoint = server.endpoint;
    let handler = HttpMessageHandler::new(move |request, _| {
        let recorded = Arc::clone(&recorded);
        async move {
            let response = if request.uri.0.contains("/login") {
                login_http_response(successful_login_response(
                    endpoint,
                    "http://caps.test/seed?token=secret",
                ))
            } else {
                login_http_response(OSD::Map(HashMap::new()))
            };
            recorded.lock().unwrap().push(request);
            response
        }
    });
    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 2_000;
    client.settings().connection_mut().mfa_enabled = true;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let callback_calls = Arc::new(AtomicUsize::new(0));
    let observed_calls = Arc::clone(&callback_calls);
    let callback = NetworkManagerLoginResponseCallback::from_handler(
        move |success, redirect, message, reason, response| {
            assert!(success);
            assert!(!redirect);
            assert_eq!(message, "Welcome");
            assert!(reason.is_empty());
            assert!(response.is_some_and(|value| value.success()));
            observed_calls.fetch_add(1, Ordering::Relaxed);
        },
    );
    manager
        .register_login_response_callback_with_login_response_callback_string_array(
            callback,
            Some(vec!["custom-login-option".into()]),
        )
        .unwrap();
    let progress = Arc::new(Mutex::new(Vec::new()));
    let observed = Arc::clone(&progress);
    let _progress_guard = manager.subscribe_login_progress(Arc::new(move |args| {
        observed.lock().unwrap().push(args.status());
    }));
    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "http://login.test/login".into();
    params.token = "123456".into();
    params.mfa_hash = "saved-mfa-hash".into();

    let response = manager
        .login_with_response_with_login_params_cancellation_token(params, None)
        .await
        .unwrap()
        .expect("successful response");
    assert_eq!(response.login(), LoginState::True);
    assert_eq!(manager.login_status_code(), LoginStatus::Success);
    assert_eq!(manager.login_message(), "Welcome");
    assert_eq!(manager.max_agent_groups(), 70);
    assert_eq!(callback_calls.load(Ordering::Relaxed), 1);
    assert!(manager.connected());
    assert!(manager.current_sim().is_some());
    assert_eq!(
        manager.login_seed_capability(),
        Some(Uri("http://caps.test/seed?token=secret".into()))
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Circuit(0x1234)
    );
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    server.commands.send(ServerCommand::ExpectEconomy).unwrap();
    assert_eq!(
        server.reports.recv_timeout(Duration::from_secs(1)).unwrap(),
        ServerReport::Economy
    );

    let login_request = requests
        .lock()
        .unwrap()
        .iter()
        .find(|request| request.uri.0.contains("/login"))
        .cloned()
        .expect("login request");
    let request = OSDParser::deserialize_with_bytes(login_request.body).unwrap();
    let OSD::Map(request) = request else {
        panic!("login request must be an LLSD map");
    };
    assert_eq!(
        request["passwd"].as_string().unwrap(),
        Utils::md5_with_string("plain-secret".into()).unwrap()
    );
    assert_eq!(request["token"].as_string().unwrap(), "123456");
    assert_eq!(request["mfa_hash"].as_string().unwrap(), "saved-mfa-hash");
    assert_eq!(request["start"].as_string().unwrap(), "last");
    assert_eq!(request["channel"].as_string().unwrap(), "MetaCrate");
    assert_eq!(request["version"].as_string().unwrap(), "1.0");
    assert_eq!(
        request["platform"].as_string().unwrap(),
        NetworkManager::get_platform().unwrap()
    );
    assert!(!request["platform_version"].as_string().unwrap().is_empty());
    assert_eq!(
        request["mac"].as_string().unwrap(),
        request["id0"].as_string().unwrap()
    );
    let OSD::Array(options) = &request["options"] else {
        panic!("login options must be an LLSD array");
    };
    assert!(
        options
            .iter()
            .any(|option| option.as_string().as_deref() == Ok("custom-login-option"))
    );
    assert_eq!(
        *progress.lock().unwrap(),
        [
            LoginStatus::ConnectingToLogin,
            LoginStatus::ReadingResponse,
            LoginStatus::ConnectingToSim,
            LoginStatus::Success,
        ]
    );
    manager
        .shutdown_with_disconnect_type(NetworkManagerDisconnectType::ClientInitiated)
        .unwrap();
}

#[tokio::test(flavor = "current_thread")]
async fn offline_fake_grid_drives_the_complete_networking_lifecycle_without_task_leaks() {
    let _network_guard = network_test_guard();
    let first_server = spawn_fake_server();
    let second_server = spawn_fake_server();

    let mut enable = EnableSimulatorMessage::new().unwrap();
    let mut simulator_info = EnableSimulatorMessageSimulatorInfoBlock::new().unwrap();
    simulator_info.ip = second_server.endpoint.ip();
    simulator_info.port = i32::from(second_server.endpoint.port());
    simulator_info.region_handle = 0x2000;
    simulator_info.region_size_x = 512;
    simulator_info.region_size_y = 256;
    enable.simulators = vec![simulator_info];
    let enable_body = enable.serialize().unwrap().snapshot();

    let release_seed = Arc::new(AtomicBool::new(false));
    let release_queue = Arc::new(AtomicBool::new(false));
    let queue_calls = Arc::new(AtomicUsize::new(0));
    let queue_cancelled = Arc::new(AtomicBool::new(false));
    let queue_done = Arc::new(AtomicBool::new(false));
    let request_uris = Arc::new(Mutex::new(Vec::new()));
    let handler = HttpMessageHandler::new({
        let release_seed = Arc::clone(&release_seed);
        let release_queue = Arc::clone(&release_queue);
        let queue_calls = Arc::clone(&queue_calls);
        let queue_cancelled = Arc::clone(&queue_cancelled);
        let queue_done = Arc::clone(&queue_done);
        let request_uris = Arc::clone(&request_uris);
        let first_endpoint = first_server.endpoint;
        move |request, cancellation| {
            let release_seed = Arc::clone(&release_seed);
            let release_queue = Arc::clone(&release_queue);
            let queue_calls = Arc::clone(&queue_calls);
            let queue_cancelled = Arc::clone(&queue_cancelled);
            let queue_done = Arc::clone(&queue_done);
            let request_uris = Arc::clone(&request_uris);
            let enable_body = enable_body.clone();
            async move {
                let uri = request.uri.0.clone();
                request_uris.lock().unwrap().push(uri.clone());
                if uri.contains("/login") {
                    return login_http_response(successful_login_response(
                        first_endpoint,
                        "https://fake.grid/seed?token=seed-secret",
                    ));
                }
                if uri.contains("/seed") {
                    while !release_seed.load(Ordering::Acquire) {
                        tokio::time::sleep(Duration::from_millis(1)).await;
                    }
                    return login_http_response(OSD::Map(HashMap::from([
                        (
                            "EventQueueGet".into(),
                            OSD::Uri(Uri("https://fake.grid/queue?token=queue-secret".into())),
                        ),
                        (
                            "GetTexture".into(),
                            OSD::Uri(Uri("https://fake.grid/asset?token=asset-secret".into())),
                        ),
                    ])));
                }
                if uri.contains("/queue") {
                    let done = matches!(
                        OSDParser::deserialize_llsd_xml_with_bytes(request.body.clone()),
                        Ok(OSD::Map(ref map))
                            if map
                                .get("done")
                                .and_then(|value| value.as_boolean().ok())
                                == Some(true)
                    );
                    if done {
                        queue_done.store(true, Ordering::Release);
                        return HttpResponse {
                            status_code: 499,
                            headers: BTreeMap::new(),
                            content_type: Some(HttpCapsClient::LLSD_XML.into()),
                            body: Vec::new(),
                        };
                    }
                    let call = queue_calls.fetch_add(1, Ordering::AcqRel);
                    if call == 0 {
                        while !release_queue.load(Ordering::Acquire) {
                            tokio::time::sleep(Duration::from_millis(1)).await;
                        }
                        return login_http_response(OSD::Map(HashMap::from([
                            ("id".into(), OSD::Integer(1)),
                            (
                                "events".into(),
                                OSD::Array(vec![OSD::Map(HashMap::from([
                                    ("message".into(), OSD::String("EnableSimulator".into())),
                                    ("body".into(), OSD::Map(enable_body)),
                                ]))]),
                            ),
                        ])));
                    }
                    let observed_cancellation = Arc::clone(&queue_cancelled);
                    let _cancellation_guard = cancellation.register_callback(Arc::new(move || {
                        observed_cancellation.store(true, Ordering::Release);
                    }));
                    cancellation.cancelled().await;
                    return HttpResponse {
                        status_code: 499,
                        headers: BTreeMap::new(),
                        content_type: Some(HttpCapsClient::LLSD_XML.into()),
                        body: Vec::new(),
                    };
                }
                if uri.contains("/asset") {
                    return HttpResponse {
                        status_code: 200,
                        headers: BTreeMap::new(),
                        content_type: Some("application/octet-stream".into()),
                        body: b"fake-grid-asset".to_vec(),
                    };
                }
                HttpResponse {
                    status_code: 404,
                    headers: BTreeMap::new(),
                    content_type: None,
                    body: Vec::new(),
                }
            }
        }
    });

    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 2_000;
    client.settings().agent_settings_mut().multiple_sims = true;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let manager_event_order = Arc::new(Mutex::new(Vec::new()));
    let (queue_sender, queue_receiver) = channel();
    let queue_order = Arc::clone(&manager_event_order);
    let _queue_guard = manager.subscribe_event_queue_running(Arc::new(move |args| {
        queue_order.lock().unwrap().push("event_queue_running");
        queue_sender.send(args.simulator().handle).unwrap();
    }));
    let (logout_sender, logout_receiver) = channel();
    let logout_order = Arc::clone(&manager_event_order);
    let _logout_guard = manager.subscribe_logged_out(Arc::new(move |args| {
        logout_order.lock().unwrap().push("logged_out");
        logout_sender.send(args.inventory_items).unwrap();
    }));

    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "https://fake.grid/login".into();
    assert!(
        manager
            .login_with_login_params_cancellation_token(params, None)
            .await
            .unwrap()
    );
    assert_eq!(
        first_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(0x1234)
    );
    assert_eq!(
        first_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    let first = manager.current_sim().expect("initial simulator");
    let caps = first.clone().caps.expect("seed capability client");
    release_seed.store(true, Ordering::Release);
    wait_until(|| {
        caps.capability_uri("GetTexture".into())
            .ok()
            .flatten()
            .is_some()
    });
    release_queue.store(true, Ordering::Release);
    assert_eq!(
        queue_receiver.recv_timeout(Duration::from_secs(2)).unwrap(),
        first.handle
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(2))
            .unwrap(),
        ServerReport::Circuit(0x1234)
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::HandshakeReply(0x1 | 0x2 | 0x4)
    );
    let second = manager
        .find_simulator_with_ip_end_point(second_server.endpoint)
        .unwrap()
        .expect("event-queue simulator");

    let downloads = DownloadManager::new(client.clone()).unwrap();
    let (_, asset) = downloads
        .queue_download_with_uri_string_i_progress_cancellation_token_int32(
            caps.capability_uri("GetTexture".into()).unwrap().unwrap(),
            None,
            None,
            None,
            Some(0),
        )
        .await
        .unwrap();
    assert_eq!(asset, b"fake-grid-asset");
    assert_eq!(downloads.active_download_count(), 0);

    // Cross-manager construction is deliberately performed only after the
    // authenticated simulator and seed capabilities are available. Every
    // manager below shares the same client runtime and must preserve its
    // local store when accessed again through GridClient.
    {
        let agent = client.self_();
        assert_eq!(
            agent.agent_id(),
            UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap()
        );
        assert_eq!(
            agent.session_id(),
            UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap()
        );
    }
    let inventory_id =
        UUID::new_with_string("44444444-4444-4444-4444-444444444444".into()).unwrap();
    let inventory = client.inventory();
    let mut store = inventory.store().expect("authenticated inventory store");
    let mut item = InventoryItem::new_with_uuid(inventory_id).unwrap();
    item.base.set_name("fake-grid inventory marker".into());
    store.set_item(inventory_id, item.base);
    assert!(
        client
            .inventory()
            .store()
            .unwrap()
            .contains_with_uuid(inventory_id)
            .unwrap()
    );
    assert!(!client.ais_client().is_available());
    assert_eq!(client.appearance().last_update_received_cof_version(), -1);
    assert!(
        client
            .assets()
            .create_asset_wrapper(AssetType::Texture)
            .is_ok()
    );
    let _avatars = client.avatars();
    assert!(client.animesh().get_player(UUID::zero()).unwrap().is_none());
    client.objects().request_object(first.clone(), 1).unwrap();
    assert!(client.parcels().current_parcel().is_none());
    assert!(client.grid().regions_read_only().is_empty());
    assert!(client.friends().friend_list().is_empty());
    assert_eq!(client.groups().current_group_count(), -1);
    assert!(client.marketplace().listings_by_id().is_empty());
    let _terrain = client.terrain();
    let _environment = client.environment();
    let _sound = client.sound();
    let _estate = client.estate();

    let cancelled_search = CancellationTokenSource::new();
    cancelled_search.cancel();
    assert!(matches!(
        client
            .directory()
            .search_people("Ada Lovelace".into(), Some(cancelled_search.token()),),
        Err(Error::Cancelled)
    ));

    let (marketplace_error_sender, marketplace_error_receiver) = channel();
    let marketplace_order = Arc::clone(&manager_event_order);
    let _marketplace_error_guard = client.marketplace().subscribe_error(Arc::new(move |_| {
        marketplace_order.lock().unwrap().push("marketplace_error");
        marketplace_error_sender.send(()).unwrap();
    }));
    client.marketplace().fetch_listings(None).await.unwrap();
    marketplace_error_receiver
        .recv_timeout(Duration::from_secs(1))
        .unwrap();

    second_server.commands.send(ServerCommand::Handoff).unwrap();
    manager
        .connect_with_ip_end_point_u_int64_boolean_uri_u_int32_u_int32(
            second_server.endpoint,
            second.handle,
            true,
            None,
            second.size_x,
            second.size_y,
        )
        .unwrap();
    assert_eq!(manager.current_sim(), Some(second.clone()));
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Circuit(0x1234)
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Movement(0x1234)
    );

    let diagnostics = format!("{manager:?} {caps:?} {downloads:?} {first:?} {second:?}");
    for secret in [
        "seed-secret",
        "queue-secret",
        "asset-secret",
        "plain-secret",
    ] {
        assert!(!diagnostics.contains(secret));
    }
    assert!(first.stats.get_recv_packets().unwrap() > 0);
    assert!(first.stats.get_sent_packets().unwrap() > 0);
    assert!(second.stats.get_recv_packets().unwrap() > 0);
    assert!(second.stats.get_sent_packets().unwrap() > 0);

    wait_until(|| queue_calls.load(Ordering::Acquire) >= 2);
    let returned_inventory =
        vec![UUID::new_with_string("33333333-3333-3333-3333-333333333333".into()).unwrap()];
    second_server
        .commands
        .send(ServerCommand::LogoutReply(returned_inventory.clone()))
        .unwrap();
    manager.logout_with_method().unwrap();
    assert_eq!(
        logout_receiver
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        returned_inventory
    );
    assert_eq!(
        second_server
            .reports
            .recv_timeout(Duration::from_secs(1))
            .unwrap(),
        ServerReport::Logout(
            UUID::new_with_string("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa".into()).unwrap(),
            UUID::new_with_string("bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb".into()).unwrap(),
        )
    );
    wait_until(|| queue_cancelled.load(Ordering::Acquire));
    assert!(!queue_done.load(Ordering::Acquire));
    assert!(!caps.is_event_queue_running());
    assert!(!manager.connected());
    assert!(manager.simulators.is_empty());
    assert_eq!(manager.pending_logout_tasks(), 0);
    assert!(!first.connected());
    assert!(!second.connected());

    downloads.dispose().unwrap();
    assert!(downloads.is_disposed());
    assert!(!downloads.dispatcher_running());
    assert_eq!(downloads.active_download_count(), 0);
    assert_eq!(
        *manager_event_order.lock().unwrap(),
        ["event_queue_running", "marketplace_error", "logged_out"]
    );
    let client_cancellation = client.cancellation_token();
    client.dispose_with_method().unwrap();
    assert!(client_cancellation.is_cancellation_requested());
    let uris = request_uris.lock().unwrap();
    assert!(uris.iter().any(|uri| uri.contains("/login")));
    assert!(uris.iter().any(|uri| uri.contains("/seed")));
    assert!(uris.iter().any(|uri| uri.contains("/queue")));
    assert!(uris.iter().any(|uri| uri.contains("/asset")));
}

#[tokio::test(flavor = "current_thread")]
async fn redirect_reuses_hashed_credentials_and_failure_is_typed_without_partial_session() {
    let requests = Arc::new(Mutex::new(Vec::<HttpRequest>::new()));
    let recorded = Arc::clone(&requests);
    let calls = Arc::new(AtomicUsize::new(0));
    let handler_calls = Arc::clone(&calls);
    let handler = HttpMessageHandler::new(move |request, _| {
        let recorded = Arc::clone(&recorded);
        let index = handler_calls.fetch_add(1, Ordering::Relaxed);
        async move {
            recorded.lock().unwrap().push(request);
            if index == 0 {
                login_http_response(OSD::Map(HashMap::from([
                    ("login".into(), OSD::String("indeterminate".into())),
                    (
                        "next_url".into(),
                        OSD::String("http://login.test/redirected".into()),
                    ),
                    ("next_duration".into(), OSD::Integer(0)),
                    ("message".into(), OSD::String("Redirecting".into())),
                ])))
            } else {
                login_http_response(OSD::Map(HashMap::from([
                    ("login".into(), OSD::Boolean(false)),
                    ("reason".into(), OSD::String("key".into())),
                    ("message".into(), OSD::String("Denied".into())),
                ])))
            }
        }
    });
    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 1_000;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let callback_calls = Arc::new(AtomicUsize::new(0));
    let observed_calls = Arc::clone(&callback_calls);
    manager
        .register_login_response_callback_with_login_response_callback(
            NetworkManagerLoginResponseCallback::from_handler(move |_, _, _, _, _| {
                observed_calls.fetch_add(1, Ordering::Relaxed);
            }),
        )
        .unwrap();
    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "http://login.test/login".into();

    assert!(
        !manager
            .login_with_login_params_cancellation_token(params, None)
            .await
            .unwrap()
    );
    assert_eq!(manager.login_status_code(), LoginStatus::Failed);
    assert_eq!(manager.login_error_key(), "key");
    assert_eq!(manager.login_message(), "Denied");
    assert!(!manager.connected());
    assert!(manager.current_sim().is_none());
    assert_eq!(
        callback_calls.load(Ordering::Relaxed),
        0,
        "the C# callback only runs for a successful login"
    );
    let passwords: Vec<_> = requests
        .lock()
        .unwrap()
        .iter()
        .map(|request| {
            let OSD::Map(map) = OSDParser::deserialize_with_bytes(request.body.clone()).unwrap()
            else {
                panic!("request must be map");
            };
            map["passwd"].as_string().unwrap()
        })
        .collect();
    assert_eq!(passwords.len(), 2);
    assert_eq!(passwords[0], passwords[1]);
    assert_ne!(passwords[0], "plain-secret");
}

#[tokio::test(flavor = "current_thread")]
async fn successful_authentication_with_invalid_simulator_clears_partial_session() {
    let handler = HttpMessageHandler::new(|_, _| async move {
        let mut response = successful_login_response(
            "127.0.0.1:1".parse().unwrap(),
            "http://caps.test/seed?token=must-not-survive",
        );
        let OSD::Map(ref mut fields) = response else {
            unreachable!();
        };
        fields.insert("sim_port".into(), OSD::Integer(0));
        login_http_response(response)
    });
    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 1_000;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "http://login.test/login".into();

    assert!(
        !manager
            .login_with_login_params_cancellation_token(params, None)
            .await
            .unwrap()
    );
    assert_eq!(manager.login_status_code(), LoginStatus::Failed);
    assert!(manager.current_sim().is_none());
    assert!(manager.simulators.is_empty());
    assert!(!manager.connected());
    assert!(manager.login_seed_capability().is_none());
    let response = manager.login_response_data.as_ref().unwrap();
    assert_eq!(response.session_id(), UUID::zero());
    assert_eq!(response.secure_session_id(), UUID::zero());
    assert!(response.seed_capability().is_empty());
    assert!(response.account_level_benefits().is_none());
}

#[tokio::test(flavor = "current_thread", start_paused = true)]
async fn login_timeout_is_typed_and_leaves_no_partial_session() {
    let handler =
        HttpMessageHandler::new(|_, _| async move { std::future::pending::<HttpResponse>().await });
    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 25;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "http://login.test/login".into();

    assert!(
        !manager
            .login_with_login_params_cancellation_token(params, None)
            .await
            .unwrap()
    );
    assert_eq!(manager.login_status_code(), LoginStatus::Failed);
    assert_eq!(manager.login_error_key(), "timeout");
    assert_eq!(manager.login_message(), "Login timed out");
    assert!(manager.current_sim().is_none());
    assert!(manager.login_seed_capability().is_none());
}

#[tokio::test(flavor = "current_thread")]
async fn canceled_login_finishes_failed_without_retaining_simulator_work() {
    let handler = HttpMessageHandler::new(|_, cancellation| async move {
        cancellation.cancelled().await;
        login_http_response(OSD::Map(HashMap::new()))
    });
    let mut client = GridClient::new().unwrap();
    client.settings().timing().login_timeout = 1_000;
    client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let manager = client.network();
    let mut params = manager
        .default_login_params(
            "Ada".into(),
            "Lovelace".into(),
            "plain-secret".into(),
            "MetaCrate".into(),
            "1.0".into(),
        )
        .unwrap();
    params.uri = "http://login.test/login".into();
    let cancellation = CancellationTokenSource::new();
    cancellation.cancel();

    assert!(
        !manager
            .login_with_login_params_cancellation_token(params, Some(cancellation.token()))
            .await
            .unwrap()
    );
    assert_eq!(manager.login_status_code(), LoginStatus::Failed);
    assert_eq!(manager.login_error_key(), "canceled");
    assert!(!manager.connected());
    assert!(manager.simulators.is_empty());
}
