#![cfg(feature = "dyn-value")]

use armour_core::dyn_value::{DecodeError, DynValue, StructValue, decode};
use armour_core::{Fields, Fuid, Id64, ScalarTyp, StructType, Typ, enc};
use rapira::Rapira;

#[test]
fn id64_roundtrip() {
    let known: u64 = 0x0123_4567_89ab_cdef;
    let id = Id64::<()>::new(known);
    let bytes = rapira::serialize(&id);
    let decoded = decode(&Typ::Scalar(ScalarTyp::Id64), &bytes).unwrap();
    assert_eq!(decoded, DynValue::Id64(known));
    let dyn_s = serde_json::to_string(&decoded).unwrap();
    let id_s = serde_json::to_string(&enc::encode(id.as_ref())).unwrap();
    assert_eq!(dyn_s, id_s);
}

#[test]
fn fuid_roundtrip() {
    let known: u64 = 0x0123_4567_89ab_cdef;
    let id = Fuid::<()>::from_be_bytes(known.to_be_bytes());
    let bytes = rapira::serialize(&id);
    let decoded = decode(&Typ::Scalar(ScalarTyp::Fuid), &bytes).unwrap();
    assert_eq!(decoded, DynValue::Fuid(known));
    let dyn_s = serde_json::to_string(&decoded).unwrap();
    let id_s = serde_json::to_string(&id.ser()).unwrap();
    assert_eq!(dyn_s, id_s);
}

#[test]
fn primitive_u64_roundtrip() {
    let v: u64 = 0xdeadbeef_cafebabe;
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Scalar(ScalarTyp::U64), &bytes).unwrap();
    assert_eq!(decoded, DynValue::U64(v));
}

#[test]
fn primitive_string_roundtrip() {
    let v: String = "hello dyn".to_string();
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Scalar(ScalarTyp::Str), &bytes).unwrap();
    assert_eq!(decoded, DynValue::Str(v));
}

#[test]
fn primitive_bool_roundtrip() {
    let bytes = rapira::serialize(&true);
    assert_eq!(
        decode(&Typ::Scalar(ScalarTyp::Bool), &bytes).unwrap(),
        DynValue::Bool(true)
    );
    let bytes = rapira::serialize(&false);
    assert_eq!(
        decode(&Typ::Scalar(ScalarTyp::Bool), &bytes).unwrap(),
        DynValue::Bool(false)
    );
}

#[test]
fn optional_some_roundtrip() {
    let v: Option<u32> = Some(42);
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Optional(&Typ::Scalar(ScalarTyp::U32)), &bytes).unwrap();
    assert_eq!(
        decoded,
        DynValue::Optional(Some(Box::new(DynValue::U32(42))))
    );
}

#[test]
fn optional_none_roundtrip() {
    let v: Option<u32> = None;
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Optional(&Typ::Scalar(ScalarTyp::U32)), &bytes).unwrap();
    assert_eq!(decoded, DynValue::Optional(None));
}

#[test]
fn vec_roundtrip() {
    let v: Vec<u16> = vec![1, 2, 3, 4];
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Vec(&Typ::Scalar(ScalarTyp::U16)), &bytes).unwrap();
    assert_eq!(
        decoded,
        DynValue::Vec(vec![
            DynValue::U16(1),
            DynValue::U16(2),
            DynValue::U16(3),
            DynValue::U16(4),
        ])
    );
}

#[test]
fn named_struct_roundtrip() {
    #[derive(Rapira)]
    struct User {
        id: u64,
        active: bool,
    }
    let u = User {
        id: 42,
        active: true,
    };
    let bytes = rapira::serialize(&u);
    let typ = Typ::Struct(StructType {
        name: "User",
        fields: Fields::Named(&[
            ("id", Typ::Scalar(ScalarTyp::U64)),
            ("active", Typ::Scalar(ScalarTyp::Bool)),
        ]),
    });
    let decoded = decode(&typ, &bytes).unwrap();
    match decoded {
        DynValue::Struct { fields, .. } => match fields {
            StructValue::Named(pairs) => {
                assert_eq!(pairs.len(), 2);
                assert_eq!(pairs[0].0, "id");
                assert_eq!(pairs[0].1, DynValue::U64(42));
                assert_eq!(pairs[1].0, "active");
                assert_eq!(pairs[1].1, DynValue::Bool(true));
            }
            _ => panic!("expected Named"),
        },
        _ => panic!("expected Struct"),
    }
}

#[test]
fn array_bytes_roundtrip() {
    let v: [u8; 4] = [0xde, 0xad, 0xbe, 0xef];
    let bytes = rapira::serialize(&v);
    let decoded = decode(&Typ::Scalar(ScalarTyp::ArrayBytes(4)), &bytes).unwrap();
    assert_eq!(decoded, DynValue::ArrayBytes(vec![0xde, 0xad, 0xbe, 0xef]));
}

#[test]
fn trailing_bytes_error() {
    let bytes = rapira::serialize(&42u32);
    let mut with_extra = bytes.clone();
    with_extra.push(0xff);
    let err = decode(&Typ::Scalar(ScalarTyp::U32), &with_extra);
    assert!(matches!(err, Err(DecodeError::TrailingBytes { .. })));
}

#[test]
fn unexpected_eof_error() {
    let bytes: Vec<u8> = vec![1, 2]; // too short for u32
    let err = decode(&Typ::Scalar(ScalarTyp::U32), &bytes);
    assert!(matches!(err, Err(DecodeError::UnexpectedEof { .. })));
}

#[test]
fn json_via_serde_roundtrip() {
    let d = DynValue::Struct {
        name: "User",
        fields: StructValue::Named(vec![
            ("id", DynValue::Fuid(0x0123_4567_89ab_cdef)),
            ("name", DynValue::Str("Alice".to_owned())),
            (
                "tags",
                DynValue::Vec(vec![
                    DynValue::Str("a".to_owned()),
                    DynValue::Str("b".to_owned()),
                ]),
            ),
        ]),
    };
    let json = serde_json::to_string(&d).unwrap();
    // Fuid rendered as zbase32 — just sanity-check that the id key exists.
    assert!(json.contains("\"id\""));
    assert!(json.contains("\"name\":\"Alice\""));
    assert!(json.contains("\"tags\":[\"a\",\"b\"]"));
}
