// Exact Rust translations of LibreMetaverse.Tests/ConstructorNullArgumentTests.cs and
// LibreMetaverse.Tests/InventoryAISClientTests.cs inventory-owned cases.
// Source SHA-256: 28deea0bf4d9353a2424842c6184abab8fe82746ceb12e093912728a247f21bc
// Source SHA-256: 6de18630ff0da2612721a9d4e28b5fdee5845c8d9477d322a8d608508762e1ff

use libremetaverse::{Error, GridClient, InventoryAISClient};
use libremetaverse_compat_tests::block_on;
use libremetaverse_structured_data::OSD;
use libremetaverse_types::{AssetType, InventoryType, SaleType, UUID};
use std::collections::HashMap;
use std::future::Future;
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use std::time::{SystemTime, UNIX_EPOCH};

fn uuid() -> UUID {
    UUID::random().expect("UUID Random")
}

fn now_seconds() -> i32 {
    i32::try_from(
        SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .expect("clock before Unix epoch")
            .as_secs(),
    )
    .expect("current Unix time fits the C# Int32 fixture")
}

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

fn permissions(creator: UUID, last_owner: UUID, owner: Option<UUID>) -> OSD {
    map([
        ("creator_id", OSD::UUID(creator)),
        ("last_owner_id", OSD::UUID(last_owner)),
        ("owner_id", OSD::UUID(owner.unwrap_or(last_owner))),
        ("base_mask", OSD::Integer(0)),
        ("everyone_mask", OSD::Integer(0)),
        ("group_mask", OSD::Integer(0)),
        ("next_owner_mask", OSD::Integer(0)),
        ("owner_mask", OSD::Integer(0)),
        ("is_owner_group", OSD::Boolean(false)),
        ("group_id", OSD::UUID(UUID::zero())),
    ])
}

fn sale_info() -> OSD {
    map([
        ("sale_price", OSD::Integer(0)),
        ("sale_type", OSD::Integer(SaleType::Not as i32)),
    ])
}

fn concrete_item(item_id: UUID, parent_id: UUID, agent_id: UUID, name: &str) -> OSD {
    map([
        ("item_id", OSD::UUID(item_id)),
        ("parent_id", OSD::UUID(parent_id)),
        ("name", OSD::String(name.into())),
        ("desc", OSD::String("desc".into())),
        ("agent_id", OSD::UUID(agent_id)),
        ("inv_type", OSD::Integer(i32::from(InventoryType::OBJECT.0))),
        ("type", OSD::Integer(AssetType::Object as i32)),
        ("created_at", OSD::Integer(now_seconds())),
        ("permissions", permissions(uuid(), uuid(), Some(agent_id))),
        ("sale_info", sale_info()),
    ])
}

fn link_item(
    item_id: UUID,
    parent_id: UUID,
    agent_id: UUID,
    linked_id: UUID,
    name: &str,
    asset_type: AssetType,
) -> OSD {
    map([
        ("item_id", OSD::UUID(item_id)),
        ("parent_id", OSD::UUID(parent_id)),
        ("name", OSD::String(name.into())),
        ("desc", OSD::String("desc".into())),
        ("agent_id", OSD::UUID(agent_id)),
        ("linked_id", OSD::UUID(linked_id)),
        (
            "inv_type",
            OSD::Integer(i32::from(InventoryType::TEXTURE.0)),
        ),
        ("type", OSD::Integer(asset_type as i32)),
        ("created_at", OSD::Integer(now_seconds())),
        ("permissions", permissions(uuid(), uuid(), None)),
        ("sale_info", sale_info()),
    ])
}

fn section(name: &'static str, value: OSD, embedded: bool) -> OSD {
    let content = map([(name, value)]);
    if embedded {
        map([("_embedded", content)])
    } else {
        content
    }
}

fn numbered(value: OSD) -> OSD {
    OSD::Map(HashMap::from([("0".into(), value)]))
}

fn client() -> InventoryAISClient {
    let grid = GridClient::new().expect("GridClient constructor");
    InventoryAISClient::new(Some(Arc::new(grid))).expect("InventoryAISClient constructor")
}

// parity-case: LibreMetaverse.Tests/ConstructorNullArgumentTests.cs::ConstructorNullArgumentTests.InventoryAISClient_Null_Throws::test ac92e375efc45c50b37e6c82aea372d605fceb470ce053a025e05ff47a5c7121 translated
#[test]
fn inventory_ais_client_null_throws() {
    match InventoryAISClient::new(None) {
        Err(error) => assert_eq!(error, Error::ArgumentNull),
        Ok(_) => panic!("expected ArgumentNull for client"),
    }
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_ObjectAsset_ResultsInAttachmentInventoryTypeAndParsesFields::test 3ae365ed34f12d036dcd44c94f8568d163cfeec2a58b4975975b77b418770131 translated
#[test]
fn parse_links_object_asset_results_in_attachment_type_and_parses_fields() {
    let item_id = uuid();
    let parent_id = uuid();
    let agent_id = uuid();
    let linked_id = uuid();
    let response = section(
        "links",
        numbered(link_item(
            item_id,
            parent_id,
            agent_id,
            linked_id,
            "TestObject",
            AssetType::Object,
        )),
        true,
    );
    let result = client()
        .parse_links_from_embedded(Some(response))
        .expect("ParseLinksFromEmbedded");
    assert_eq!(result.len(), 1);
    let parsed = &result[0];
    assert_eq!(parsed.base.name(), "TestObject");
    assert_eq!(parsed.base.parent_uuid(), parent_id);
    assert_eq!(parsed.asset_uuid(), linked_id);
    assert_eq!(parsed.base.owner_id(), agent_id);
    assert_ne!(parsed.creation_date(), UNIX_EPOCH);
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_ItemsKey_ParsesConcreteItems::test d9e7c64106639071e1ec6ada7346bfa050a31183becaac79efefcd4755fe2855 translated
#[test]
fn parse_items_items_key_parses_concrete_items() {
    let item_id = uuid();
    let parent_id = uuid();
    let agent_id = uuid();
    let response = section(
        "items",
        numbered(concrete_item(item_id, parent_id, agent_id, "ConcreteItem")),
        true,
    );
    let result = client()
        .parse_items_from_embedded(Some(response))
        .expect("ParseItemsFromEmbedded");
    assert_eq!(result.len(), 1);
    let parsed = &result[0];
    assert_eq!(parsed.base.name(), "ConcreteItem");
    assert_eq!(parsed.base.parent_uuid(), parent_id);
    assert_eq!(parsed.base.owner_id(), agent_id);
    assert_ne!(parsed.creation_date(), UNIX_EPOCH);
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_MeshAsset_TreatedAsAttachment::test f807110d577c654128dd8d42e618c610c260c4f3367cf2795776564409f876a2 translated
#[test]
fn parse_links_mesh_asset_treated_as_attachment() {
    let item_id = uuid();
    let parent_id = uuid();
    let agent_id = uuid();
    let linked_id = uuid();
    let response = section(
        "links",
        numbered(link_item(
            item_id,
            parent_id,
            agent_id,
            linked_id,
            "MeshLink",
            AssetType::Mesh,
        )),
        false,
    );
    let result = client()
        .parse_links_from_embedded(Some(response))
        .expect("ParseLinksFromEmbedded");
    assert_eq!(result.len(), 1);
    let parsed = &result[0];
    assert_eq!(parsed.base.name(), "MeshLink");
    assert_eq!(parsed.base.parent_uuid(), parent_id);
    assert_eq!(parsed.asset_uuid(), linked_id);
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_TopLevelItemsKey_WorksWithoutEmbedded::test 9c8f2090d59ff5e75e75381a7283c501cadd56e591991cdead23c504738a0a6d translated
#[test]
fn parse_items_top_level_items_key_works_without_embedded() {
    let response = section(
        "items",
        numbered(concrete_item(uuid(), uuid(), uuid(), "TopLevelItem")),
        false,
    );
    let result = client()
        .parse_items_from_embedded(Some(response))
        .expect("ParseItemsFromEmbedded");
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].base.name(), "TopLevelItem");
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseLinksFromEmbedded_TopLevelLinksKey_WorksWithoutEmbedded::test 29afd2bd6a554959d6cfa99b632672d56bbb5bcad2ccbaf8caf4773a514f5d79 translated
#[test]
fn parse_links_top_level_links_key_works_without_embedded() {
    let response = section(
        "links",
        numbered(link_item(
            uuid(),
            uuid(),
            uuid(),
            uuid(),
            "TopLevelLink",
            AssetType::Object,
        )),
        false,
    );
    let result = client()
        .parse_links_from_embedded(Some(response))
        .expect("ParseLinksFromEmbedded");
    assert_eq!(result.len(), 1);
    assert_eq!(result[0].base.name(), "TopLevelLink");
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseItemsFromEmbedded_NullOrEmpty_ReturnsEmptyList::test 198fb619bbf396a4b1a7b4b85b7fba8808ae127e2575b2b84d21f01a2bdbc445 translated
#[test]
fn parse_items_null_or_empty_returns_empty_list() {
    let ais = client();
    assert!(
        ais.parse_items_from_embedded(None)
            .expect("ParseItemsFromEmbedded null")
            .is_empty()
    );
    assert!(
        ais.parse_items_from_embedded(Some(OSD::Map(HashMap::new())))
            .expect("ParseItemsFromEmbedded empty")
            .is_empty()
    );
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.ParseEmbedded_CombinesItemsAndLinks::test f2ebf4638c9aef3c846dc182d97d80c95118799428cf8c2bba86163e33196846 translated
#[test]
fn parse_embedded_combines_items_and_links() {
    let item = concrete_item(uuid(), uuid(), uuid(), "I");
    let link = link_item(uuid(), uuid(), uuid(), uuid(), "L", AssetType::Object);
    let response = map([(
        "_embedded",
        map([("items", numbered(item)), ("links", numbered(link))]),
    )]);
    let mut folders = None;
    let mut items = None;
    let mut links = None;
    client()
        .parse_embedded(response, &mut folders, &mut items, &mut links)
        .expect("ParseEmbedded");
    assert_eq!(items.expect("items output").len(), 1);
    assert_eq!(links.expect("links output").len(), 1);
    assert!(folders.is_some(), "folders output must not be null");
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_AlwaysReturnsFalse::test bebad05897efc0da2f959b4f567faa3ffee950520d13265ecbab1dee978f4886 translated
#[test]
fn move_category_links_always_returns_false() {
    let result = block_on(client().move_category_links("outfit".into(), uuid(), None))
        .expect("MoveCategoryLinksAsync");
    assert!(!result, "AIS v3 has no MoveCategoryLinks equivalent");
}

// parity-case: LibreMetaverse.Tests/InventoryAISClientTests.cs::InventoryAISClientTests.MoveCategoryLinksAsync_ReturnsCompletedTask::test f9830b11e6a75c90f8394a5ade56bbd542c140be4c050ba7db6c9ee4d2b7a699 translated
#[test]
fn move_category_links_returns_completed_future() {
    let ais = client();
    let mut future = std::pin::pin!(ais.move_category_links("outfit".into(), uuid(), None));
    let mut context = Context::from_waker(Waker::noop());
    assert!(
        matches!(future.as_mut().poll(&mut context), Poll::Ready(Ok(false))),
        "MoveCategoryLinksAsync should be immediately ready with false"
    );
}
