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

use armour_core::ScalarTyp;
use armour_core::{GetType, SchemaFields, SchemaTyp};
use rapira::{FromU8, PeerRead, 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 as PeerRead>::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 as PeerRead>::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 as PeerRead>::from_slice_with_peer(&mut s, &peer).unwrap();
    assert_eq!(p, Pair(3, 4));
    assert!(s.is_empty());
}

#[derive(Rapira, GetType, Copy, Clone, 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 as PeerRead>::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 as PeerRead>::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 as PeerRead>::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 as PeerRead>::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 as PeerRead>::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 as PeerRead>::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");
}

#[derive(Rapira, GetType, Debug, PartialEq)]
#[rapira(peer)]
enum Content {
    Text(String),
    #[idx = 2]
    #[rapira(unknown)]
    Unsupported,
}

#[test]
fn complex_enum_unknown_variant_skips_payload_and_keeps_vec_cursor() {
    let enum_peer = SchemaTyp::Enum {
        name: "Content".to_string(),
        variants: vec![
            (0, "Text".to_string(), SchemaTyp::Scalar(ScalarTyp::Str)),
            (1, "Image".to_string(), SchemaTyp::Scalar(ScalarTyp::Bytes)),
        ],
    };
    let peer = SchemaTyp::Vec(Box::new(enum_peer.clone()));

    let mut bytes = serialize(&3u32);
    bytes.push(0);
    bytes.extend_from_slice(&serialize(&String::from("hello")));
    bytes.push(1);
    bytes.extend_from_slice(&serialize(&vec![1u8, 2, 3]));
    bytes.push(0);
    bytes.extend_from_slice(&serialize(&String::from("tail")));

    let mut slice = bytes.as_slice();
    let got = <Vec<Content> as PeerRead>::from_slice_with_peer(&mut slice, &peer).unwrap();
    assert!(slice.is_empty());
    assert_eq!(
        got,
        vec![
            Content::Text("hello".to_string()),
            Content::Unsupported,
            Content::Text("tail".to_string()),
        ]
    );
}

#[derive(Rapira, GetType, Debug, PartialEq)]
#[rapira(peer)]
enum ComplexSameTagNameMismatch {
    Text(String),
    #[rapira(unknown)]
    Unsupported,
}

#[test]
fn complex_enum_same_tag_name_mismatch_is_schema_mismatch() {
    let peer = SchemaTyp::Enum {
        name: "ComplexSameTagNameMismatch".to_string(),
        variants: vec![(0, "Image".to_string(), SchemaTyp::Scalar(ScalarTyp::Str))],
    };

    let mut bytes = vec![0u8];
    bytes.extend_from_slice(&serialize(&"hello".to_string()));
    let mut slice = bytes.as_slice();
    let err = <ComplexSameTagNameMismatch as PeerRead>::from_slice_with_peer(&mut slice, &peer)
        .unwrap_err();
    assert!(matches!(err, rapira::RapiraError::SchemaMismatch));
}

#[derive(Rapira, FromU8, GetType, Copy, Clone, Debug, PartialEq)]
#[rapira(peer)]
enum SimpleKind {
    A,
    B,
    #[rapira(unknown)]
    Unknown,
}

#[test]
fn simple_enum_unknown_variant_maps_to_unit_fallback() {
    let peer = SchemaTyp::SimpleEnum {
        name: "SimpleKind".to_string(),
        variants: vec![
            (0, "A".to_string()),
            (1, "B".to_string()),
            (2, "C".to_string()),
        ],
    };

    let mut slice = [2u8].as_slice();
    let got = <SimpleKind as PeerRead>::from_slice_with_peer(&mut slice, &peer).unwrap();
    assert_eq!(got, SimpleKind::Unknown);
    assert!(slice.is_empty());
}

#[derive(Rapira, FromU8, GetType, Copy, Clone, Debug, PartialEq)]
#[rapira(peer)]
enum SimpleSameTagNameMismatch {
    A,
    B,
    #[rapira(unknown)]
    Unknown,
}

#[derive(Rapira, FromU8, GetType, Copy, Clone, Debug, PartialEq)]
#[repr(u8)]
#[rapira(peer)]
enum SparseSimpleKind {
    A = 7,
    B = 9,
    #[rapira(unknown)]
    Unknown = 255,
}

#[test]
fn simple_enum_peerread_uses_real_discriminants() {
    let peer = SchemaTyp::SimpleEnum {
        name: "SparseSimpleKind".to_string(),
        variants: vec![
            (7, "A".to_string()),
            (9, "B".to_string()),
            (10, "C".to_string()),
        ],
    };

    let mut known = [9u8].as_slice();
    let got = <SparseSimpleKind as PeerRead>::from_slice_with_peer(&mut known, &peer).unwrap();
    assert_eq!(got, SparseSimpleKind::B);
    assert!(known.is_empty());

    let mut unknown = [10u8].as_slice();
    let got = <SparseSimpleKind as PeerRead>::from_slice_with_peer(&mut unknown, &peer).unwrap();
    assert_eq!(got, SparseSimpleKind::Unknown);
    assert!(unknown.is_empty());
}

#[test]
fn simple_enum_same_tag_name_mismatch_is_schema_mismatch() {
    let peer = SchemaTyp::SimpleEnum {
        name: "SimpleSameTagNameMismatch".to_string(),
        variants: vec![(0, "C".to_string()), (1, "B".to_string())],
    };

    let mut slice = [0u8].as_slice();
    let err = <SimpleSameTagNameMismatch as PeerRead>::from_slice_with_peer(&mut slice, &peer)
        .unwrap_err();
    assert!(matches!(err, rapira::RapiraError::SchemaMismatch));
}

#[derive(Rapira, GetType, Debug, PartialEq)]
#[rapira(peer)]
enum NoUnknownContent {
    Text(String),
}

#[test]
fn enum_without_unknown_still_rejects_peer_variant_name() {
    let peer = SchemaTyp::Enum {
        name: "NoUnknownContent".to_string(),
        variants: vec![(1, "Image".to_string(), SchemaTyp::Scalar(ScalarTyp::Bytes))],
    };
    let mut bytes = vec![1u8];
    bytes.extend_from_slice(&serialize(&vec![1u8, 2, 3]));

    let mut slice = bytes.as_slice();
    let err = <NoUnknownContent as PeerRead>::from_slice_with_peer(&mut slice, &peer).unwrap_err();
    assert!(matches!(err, rapira::RapiraError::UnknownVariant));
}

#[derive(Rapira, GetType, Default, Debug, PartialEq)]
#[rapira(peer)]
struct BuiltinLeaves {
    b: bool,
    #[get_type(with_type = core::primitive::u8)]
    #[rapira(with = rapira::byte_rapira)]
    u8v: u8,
    u16v: u16,
    u32v: u32,
    u64v: u64,
    usizev: usize,
    i32v: i32,
    i64v: i64,
    f32v: f32,
    f64v: f64,
    text: String,
    #[cfg(feature = "uuid")]
    uuid: uuid::Uuid,
    bytes: bytes::Bytes,
    json: serde_json::Value,
    array_string: arrayvec::ArrayString<16>,
    raw: [u8; 4],
}

#[test]
fn builtin_leaf_peerread_impls_compile_and_read_exact_schema() {
    let value = BuiltinLeaves {
        b: true,
        u8v: 1,
        u16v: 2,
        u32v: 3,
        u64v: 4,
        usizev: 5,
        i32v: -6,
        i64v: -7,
        f32v: 1.25,
        f64v: 2.5,
        text: "hello".to_string(),
        #[cfg(feature = "uuid")]
        uuid: uuid::Uuid::nil(),
        bytes: bytes::Bytes::from_static(b"abc"),
        json: serde_json::json!({"a": [1, true]}),
        array_string: arrayvec::ArrayString::from("array").unwrap(),
        raw: [1, 2, 3, 4],
    };
    let peer = SchemaTyp::from(&BuiltinLeaves::TYPE);
    let bytes = serialize(&value);
    let mut slice = bytes.as_slice();

    let got = <BuiltinLeaves as PeerRead>::from_slice_with_peer(&mut slice, &peer).unwrap();
    assert_eq!(got, value);
    assert!(slice.is_empty());

    macro_rules! assert_leaf_roundtrip {
        ($ty:ty, $value:expr, $peer:expr) => {{
            let value: $ty = $value;
            let peer: SchemaTyp = $peer;
            let bytes = serialize(&value);
            let mut slice = bytes.as_slice();

            let got = <$ty as PeerRead>::from_slice_with_peer(&mut slice, &peer).unwrap();
            assert_eq!(got, value);
            assert!(slice.is_empty());
        }};
    }

    // NonZero values cannot be defaulted by generated peer structs when a peer
    // schema omits the field, so keep them as exact leaf coverage.
    assert_leaf_roundtrip!(
        core::num::NonZeroU32,
        core::num::NonZeroU32::new(8).unwrap(),
        SchemaTyp::from(&<core::num::NonZeroU32 as GetType>::TYPE)
    );
    assert_leaf_roundtrip!(
        core::num::NonZeroU64,
        core::num::NonZeroU64::new(9).unwrap(),
        SchemaTyp::from(&<core::num::NonZeroU64 as GetType>::TYPE)
    );
    assert_leaf_roundtrip!(
        std::net::IpAddr,
        std::net::IpAddr::V6(std::net::Ipv6Addr::LOCALHOST),
        SchemaTyp::from(&<std::net::IpAddr as GetType>::TYPE)
    );
    assert_leaf_roundtrip!(
        std::net::SocketAddrV6,
        std::net::SocketAddrV6::new(std::net::Ipv6Addr::LOCALHOST, 443, 0, 0),
        SchemaTyp::from(&<std::net::SocketAddrV6 as GetType>::TYPE)
    );
}
