use jacquard_common::Bos;
use jacquard_derive::open_union;
use serde::{Deserialize, Serialize};
extern crate alloc;

#[open_union]
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(tag = "$type")]
#[serde(bound(
    serialize = "S: Serialize + Bos<str> + AsRef<str>",
    deserialize = "S: Deserialize<'de> + Bos<str> + AsRef<str>"
))]
enum TestUnion<S: Bos<str> + AsRef<str> = jacquard_common::DefaultStr> {
    #[serde(rename = "com.example.typeA")]
    TypeA { value: S },
    #[serde(rename = "com.example.typeB")]
    TypeB { count: i64 },
}

#[test]
fn test_open_union_known_variant() {
    let json = r#"{"$type":"com.example.typeA","value":"hello"}"#;
    let union: TestUnion = serde_json::from_str(json).unwrap();

    match union {
        TestUnion::TypeA { value } => assert_eq!(AsRef::<str>::as_ref(&value), "hello"),
        _ => panic!("expected TypeA"),
    }
}

#[test]
fn test_open_union_unknown_variant() {
    use jacquard_common::types::value::Data;

    let json = r#"{"$type":"com.example.unknown","data":"something"}"#;
    let union: TestUnion = serde_json::from_str(json).unwrap();

    match union {
        TestUnion::Unknown(Data::Object(obj)) => {
            assert!(obj.0.contains_key("$type"));
            assert!(obj.0.contains_key("data"));

            if let Some(Data::String(type_str)) = obj.0.get("$type") {
                assert_eq!(type_str.as_str(), "com.example.unknown");
            } else {
                panic!("expected $type field to be a string");
            }

            if let Some(Data::String(data_str)) = obj.0.get("data") {
                assert_eq!(data_str.as_str(), "something");
            } else {
                panic!("expected data field to be a string");
            }
        }
        _ => panic!("expected Unknown variant with Object data"),
    }
}

#[test]
fn test_open_union_roundtrip() {
    let union = TestUnion::<jacquard_common::DefaultStr>::TypeB { count: 42 };
    let json = serde_json::to_string(&union).unwrap();
    let parsed: TestUnion = serde_json::from_str(&json).unwrap();

    assert_eq!(union, parsed);
    assert!(json.contains(r#""$type":"com.example.typeB""#));
}

#[test]
fn test_open_union_unknown_roundtrip() {
    use jacquard_common::DefaultStr;
    use jacquard_common::types::value::{Data, Object};
    use std::collections::BTreeMap;

    let mut map: BTreeMap<jacquard_common::deps::smol_str::SmolStr, Data<DefaultStr>> =
        BTreeMap::new();
    map.insert(
        "$type".into(),
        Data::String(jacquard_common::types::string::AtprotoStr::String(
            jacquard_common::deps::smol_str::SmolStr::from("com.example.custom"),
        )),
    );
    map.insert("field1".into(), Data::Integer(123));
    map.insert("field2".into(), Data::Boolean(false));

    let union: TestUnion = TestUnion::Unknown(Data::Object(Object(map)));

    let json = serde_json::to_string(&union).unwrap();
    let parsed: TestUnion = serde_json::from_str(&json).unwrap();

    match parsed {
        TestUnion::Unknown(Data::Object(obj)) => {
            assert_eq!(obj.0.len(), 3);
            assert!(obj.0.contains_key("$type"));
            assert!(obj.0.contains_key("field1"));
            assert!(obj.0.contains_key("field2"));

            if let Some(Data::String(s)) = obj.0.get("$type") {
                assert_eq!(s.as_str(), "com.example.custom");
            } else {
                panic!("expected $type to be a string");
            }

            if let Some(Data::Integer(n)) = obj.0.get("field1") {
                assert_eq!(*n, 123);
            } else {
                panic!("expected field1 to be an integer");
            }

            if let Some(Data::Boolean(b)) = obj.0.get("field2") {
                assert_eq!(*b, false);
            } else {
                panic!("expected field2 to be a boolean");
            }
        }
        _ => panic!("expected Unknown variant"),
    }
}
