#![cfg(feature = "schema")]

use armour_core::ScalarTyp;
use armour_core::{GetType, SchemaFields, SchemaTyp};
use rapira::{Rapira, serialize};

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct OldMsg {
    a: u32,
}

#[test]
fn derived_new_sender_old_receiver_skips_extra() {
    let peer = SchemaTyp::Struct {
        name: "OldMsg".into(),
        fields: SchemaFields::Named(vec![
            ("a".into(), SchemaTyp::Scalar(ScalarTyp::U32)),
            ("b".into(), SchemaTyp::Scalar(ScalarTyp::Str)),
        ]),
    };
    let mut bytes = serialize(&7u32);
    bytes.extend_from_slice(&serialize(&String::from("hi")));
    let mut s = bytes.as_slice();
    let msg = OldMsg::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(msg, OldMsg { a: 7 });
    assert!(s.is_empty());
}

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct NewMsg {
    a: u32,
    b: u64,
}

#[test]
fn derived_old_sender_new_receiver_defaults_missing() {
    let peer = SchemaTyp::Struct {
        name: "NewMsg".into(),
        fields: SchemaFields::Named(vec![("a".into(), SchemaTyp::Scalar(ScalarTyp::U32))]),
    };
    let bytes = serialize(&5u32);
    let mut s = bytes.as_slice();
    let msg = NewMsg::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(msg, NewMsg { a: 5, b: 0 });
}

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct Pair(u32, u16);

#[test]
fn derived_tuple_append_only() {
    let peer = SchemaTyp::Struct {
        name: "Pair".into(),
        fields: SchemaFields::Unnamed(vec![
            SchemaTyp::Scalar(ScalarTyp::U32),
            SchemaTyp::Scalar(ScalarTyp::U16),
            SchemaTyp::Scalar(ScalarTyp::U8),
        ]),
    };
    let mut bytes = serialize(&3u32);
    bytes.extend_from_slice(&serialize(&4u16));
    bytes.push(9);
    let mut s = bytes.as_slice();
    let p = Pair::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(p, Pair(3, 4));
    assert!(s.is_empty());
}

#[derive(Rapira, GetType, Debug, PartialEq)]
#[rapira(peer)]
enum Ev {
    A(u32),
    B(u16),
}

#[derive(Rapira, GetType, Debug, PartialEq)]
#[rapira(peer)]
enum TagOnly {
    A,
    B(u16),
}

#[test]
fn derived_unit_variant_exact_void_peer() {
    let peer = SchemaTyp::Enum {
        name: "TagOnly".into(),
        variants: vec![(0, "A".into(), SchemaTyp::Scalar(ScalarTyp::Void))],
    };
    let mut s = [0u8].as_slice();
    let v = TagOnly::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(v, TagOnly::A);
    assert!(s.is_empty());
}

#[test]
fn derived_unit_variant_mismatch_on_peer_payload() {
    // Peer declares u32 payload for unit variant A — shape_compat(Void, U32) → Mismatch.
    let peer = SchemaTyp::Enum {
        name: "TagOnly".into(),
        variants: vec![(0, "A".into(), SchemaTyp::Scalar(ScalarTyp::U32))],
    };
    let mut bytes = vec![0u8];
    bytes.extend_from_slice(&serialize(&42u32));
    let mut s = bytes.as_slice();
    let r = TagOnly::from_slice_with_peer(&mut s, &peer);
    assert!(matches!(r, Err(rapira::RapiraError::SchemaMismatch)));
}

#[test]
fn derived_enum_known_variant() {
    let peer = SchemaTyp::Enum {
        name: "Ev".into(),
        variants: vec![
            (0, "A".into(), SchemaTyp::Scalar(ScalarTyp::U32)),
            (1, "B".into(), SchemaTyp::Scalar(ScalarTyp::U16)),
        ],
    };
    let mut bytes = vec![0u8];
    bytes.extend_from_slice(&serialize(&11u32));
    let mut s = bytes.as_slice();
    let e = Ev::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(e, Ev::A(11));
}

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct Wrap {
    items: Vec<u32>,
}

#[test]
fn vec_unchanged_is_exact() {
    let peer = SchemaTyp::Struct {
        name: "Wrap".into(),
        fields: SchemaFields::Named(vec![(
            "items".into(),
            SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(ScalarTyp::U32))),
        )]),
    };
    let bytes = serialize(&vec![1u32, 2, 3]);
    let mut s = bytes.as_slice();
    let w = Wrap::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(
        w,
        Wrap {
            items: vec![1, 2, 3]
        }
    );
}

#[test]
fn vec_elem_type_change_is_mismatch() {
    let peer = SchemaTyp::Struct {
        name: "Wrap".into(),
        fields: SchemaFields::Named(vec![(
            "items".into(),
            SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(ScalarTyp::U64))),
        )]),
    };
    let bytes = serialize(&vec![1u64]);
    let mut s = bytes.as_slice();
    let r = Wrap::from_slice_with_peer(&mut s, &peer);
    assert!(matches!(r, Err(rapira::RapiraError::SchemaMismatch)));
}

#[derive(Rapira, Default, Debug, PartialEq)]
struct Gen<T: Rapira + Default> {
    x: T,
}

#[test]
fn generic_rapira_compiles_under_schema() {
    let g = Gen::<u32> { x: 5 };
    assert_eq!(
        rapira::deserialize::<Gen<u32>>(&rapira::serialize(&g)).unwrap(),
        g
    );
}

// §4.2: recursion into a CONCRETE nested struct that evolved. This is the headline
// Design-Y capability — the `Compatible` branch of the generated reconciler.
#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct Inner {
    x: u32,
}

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct Outer {
    inner: Inner,
    tail: u16,
}

#[test]
fn derived_nested_concrete_struct_compatible_recurses() {
    // peer: Inner gained a field `extra: String`. The `inner` field of Outer is therefore
    // `Compatible` (not Exact) → the reconciler must recurse into Inner's peer reader,
    // which reads `x` and skips the unknown `extra`. Then `tail` reads normally.
    let peer = SchemaTyp::Struct {
        name: "Outer".into(),
        fields: SchemaFields::Named(vec![
            (
                "inner".into(),
                SchemaTyp::Struct {
                    name: "Inner".into(),
                    fields: SchemaFields::Named(vec![
                        ("x".into(), SchemaTyp::Scalar(ScalarTyp::U32)),
                        ("extra".into(), SchemaTyp::Scalar(ScalarTyp::Str)),
                    ]),
                },
            ),
            ("tail".into(), SchemaTyp::Scalar(ScalarTyp::U16)),
        ]),
    };
    // Wire bytes in peer order: inner.x=7, inner.extra="hi", tail=9.
    let mut bytes = serialize(&7u32);
    bytes.extend_from_slice(&serialize(&String::from("hi")));
    bytes.extend_from_slice(&serialize(&9u16));
    let mut s = bytes.as_slice();
    let out = Outer::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(
        out,
        Outer {
            inner: Inner { x: 7 },
            tail: 9
        }
    );
    assert!(s.is_empty(), "extra nested field must have been skipped");
}
