// Exact Rust translations of LibreMetaverse.Tests/OfflineMessagesTests.cs and
// LibreMetaverse.Tests/ProductInfoRequestMessageTests.cs.
// Source SHA-256: ad649e9efb2b60b8a323d3d457be568fda9cc5ecb80b8999f6066d0b7f5a6527
// Source SHA-256: fabdc141e6f616b88d998ceca16f1884d4c3aa6efc5152060e8840a4ef0d6db7

use libremetaverse::messages::linden::{
    ProductInfoRequestMessage, ProductInfoRequestMessageProductInfo,
};
use libremetaverse::{GridClient, HttpCapsClient, Simulator};
use libremetaverse_compat_tests::block_on;
use libremetaverse_structured_data::{OSD, OSDMap};
use libremetaverse_types::compat::{HttpMessageHandler, HttpResponse, Uri};
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};

const CAP: &str = "http://test.invalid/read-offline-msgs";
const SEED: &str = "http://test.invalid/seed";

fn response(body: &str) -> HttpResponse {
    HttpResponse {
        status_code: 200,
        headers: BTreeMap::new(),
        content_type: Some("application/json".into()),
        body: body.as_bytes().to_vec(),
    }
}

fn offline_client(body: &str) -> GridClient {
    let body = body.to_owned();
    let handler = HttpMessageHandler::new(move |request, _| {
        let reply = if request.uri.0 == SEED {
            HttpResponse {
                status_code: 200,
                headers: BTreeMap::new(),
                content_type: Some("application/llsd+xml".into()),
                body: format!(
                    "<llsd><map><key>ReadOfflineMsgs</key><uri>{CAP}</uri><key>AcceptFriendship</key><uri>http://test.invalid/accept-friendship</uri><key>AcceptGroupInvite</key><uri>http://test.invalid/accept-group-invite</uri></map></llsd>"
                )
                .into_bytes(),
            }
        } else {
            response(&body)
        };
        async move { reply }
    });
    let mut client = GridClient::new().expect("GridClient constructor");
    client.set_http_caps_client(HttpCapsClient::new(handler.clone()).unwrap());
    let mut simulator_client = GridClient::new().expect("simulator GridClient constructor");
    simulator_client.set_http_caps_client(HttpCapsClient::new(handler).unwrap());
    let simulator = Simulator::new(
        simulator_client,
        "127.0.0.1:13".parse().unwrap(),
        0,
        None,
        None,
    )
    .expect("Simulator constructor");
    simulator
        .set_seed_caps(Some(Uri(SEED.into())), Some(true))
        .unwrap();
    let mut network = client.network();
    network.set_current_sim(Some(simulator));
    client.set_network(network);
    client
}

fn message_json(from_name: &str, text: &str) -> String {
    format!(
        "{{\"from_agent_id\":\"11111111-1111-1111-1111-111111111111\",\"from_agent_name\":\"{from_name}\",\"to_agent_id\":\"22222222-2222-2222-2222-222222222222\",\"region_id\":\"33333333-3333-3333-3333-333333333333\",\"dialog\":0,\"transaction-id\":\"44444444-4444-4444-4444-444444444444\",\"timestamp\":0,\"message\":\"{text}\",\"local_x\":1.0,\"local_y\":2.0,\"local_z\":3.0}}"
    )
}

fn retrieve(body: &str) -> Vec<(String, String)> {
    let mut client = offline_client(body);
    let received = Arc::new(Mutex::new(Vec::new()));
    let captured = Arc::clone(&received);
    let agent = client.self_();
    let _subscription = agent.subscribe_im(Arc::new(move |args| {
        let message = args.im();
        captured
            .lock()
            .unwrap()
            .push((message.message, message.from_agent_name));
    }));
    block_on(agent.retrieve_instant_messages(None)).unwrap();
    received.lock().unwrap().clone()
}

fn map(entries: impl IntoIterator<Item = (&'static str, OSD)>) -> OSDMap {
    OSDMap::new_with_dictionary(
        entries
            .into_iter()
            .map(|(key, value)| (key.into(), value))
            .collect(),
    )
    .expect("OSDMap dictionary constructor")
}

// parity-case: LibreMetaverse.Tests/OfflineMessagesTests.cs::OfflineMessagesTests.RetrieveInstantMessagesAsync_BareArrayOfArraysResponse_RaisesInstantMessage::test d51f69e0fa1e1e59a5cedec7d60315d1f590848672a9f4f0c5e21d2abff5c372 translated
#[test]
fn retrieve_offline_messages_bare_array_raises_instant_message() {
    let received = retrieve(&format!("[[{}]]", message_json("Jim", "hello")));
    assert_eq!(received.len(), 1);
    assert_eq!(received[0].0, "hello");
    assert_eq!(received[0].1, "Jim");
}

// parity-case: LibreMetaverse.Tests/OfflineMessagesTests.cs::OfflineMessagesTests.RetrieveInstantMessagesAsync_MessagesMapResponse_RaisesInstantMessage::test dbce6320d57d12ca5557cfba65257e15be314cde947b20dd21bc386819fcb3ce translated
#[test]
fn retrieve_offline_messages_map_raises_instant_message() {
    let received = retrieve(&format!(
        "{{\"messages\":[{}]}}",
        message_json("Jim", "hello via map")
    ));
    assert_eq!(received.len(), 1);
    assert_eq!(received[0].0, "hello via map");
}

// parity-case: LibreMetaverse.Tests/ProductInfoRequestMessageTests.cs::ProductInfoRequestMessageTests.Deserialize_BareArray_ReadsSkuNameDescription::test 5d51fdbb1bec1ff74df59778d214413e78fb650679d36e7c44be92d1b2e3205f translated
#[test]
fn product_info_deserialize_bare_array_reads_fields() {
    let product = OSD::Map(HashMap::from([
        ("sku".into(), OSD::String("023".into())),
        ("name".into(), OSD::String("Homestead".into())),
        (
            "description".into(),
            OSD::String("A Homestead region".into()),
        ),
    ]));
    let mut message =
        ProductInfoRequestMessage::new().expect("ProductInfoRequestMessage constructor");
    message
        .deserialize(map([("products", OSD::Array(vec![product]))]))
        .unwrap();
    assert_eq!(message.products.len(), 1);
    assert_eq!(message.products[0].sku, "023");
    assert_eq!(message.products[0].name, "Homestead");
    assert_eq!(message.products[0].description, "A Homestead region");
}

// parity-case: LibreMetaverse.Tests/ProductInfoRequestMessageTests.cs::ProductInfoRequestMessageTests.SerializeDeserialize_RoundTrips::test 5cb54bed4150cf13f815b223d6b7e35e7427004a12e98c7328c7674b6fe6309b translated
#[test]
fn product_info_serialize_deserialize_round_trips() {
    let mut product = ProductInfoRequestMessageProductInfo::new().expect("ProductInfo constructor");
    product.sku = "001".into();
    product.name = "Full Region".into();
    product.description = "A full region".into();
    let mut message =
        ProductInfoRequestMessage::new().expect("ProductInfoRequestMessage constructor");
    message.products.push(product);
    let encoded = message.serialize().unwrap();
    let mut decoded =
        ProductInfoRequestMessage::new().expect("ProductInfoRequestMessage constructor");
    decoded.deserialize(encoded).unwrap();
    assert_eq!(decoded.products.len(), 1);
    assert_eq!(decoded.products[0].sku, "001");
    assert_eq!(decoded.products[0].name, "Full Region");
    assert_eq!(decoded.products[0].description, "A full region");
}
