use msgpacker::{MsgPacker, MsgPackerBorrowed, Packable as _, Unpackable, UnpackableBorrowed};
use proptest::prelude::*;

mod utils;

#[test]
fn vec_u8_uses_binary_format() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    struct WithBytes {
        data: Vec<u8>,
    }

    let val = WithBytes {
        data: vec![0xde, 0xad, 0xbe, 0xef],
    };

    let mut bytes = vec![];
    let n = val.pack(&mut bytes);
    assert_eq!(n, bytes.len());

    // The struct has a single Vec<u8> field, so the serialized output should
    // start with a msgpack binary format tag (BIN8=0xc4, BIN16=0xc5, BIN32=0xc6),
    // NOT an array format tag (0x90..=0x9f / 0xdc / 0xdd).
    let tag = bytes[0];
    assert!(
        matches!(tag, 0xc4 | 0xc5 | 0xc6,),
        "expected binary format tag (0xc4..=0xc6), got 0x{:02x}",
        tag,
    );

    // For 4 bytes the format should be BIN8 (0xc4), length 4, then the payload.
    assert_eq!(bytes[0], 0xc4); // BIN8
    assert_eq!(bytes[1], 4); // length
    assert_eq!(&bytes[2..6], &[0xde, 0xad, 0xbe, 0xef]);

    // Round-trip via slice and iterator.
    let (o, deserialized) = WithBytes::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(o, n);
    assert_eq!(val, deserialized);

    let (p, deserialized_iter) = WithBytes::unpack_iter(bytes).unwrap();
    assert_eq!(p, n);
    assert_eq!(val, deserialized_iter);
}

#[test]
fn vec_string_uses_array_format() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    struct WithStrings {
        data: Vec<String>,
    }

    let val = WithStrings {
        data: vec!["hello".into(), "world".into()],
    };

    let mut bytes = vec![];
    let n = val.pack(&mut bytes);
    assert_eq!(n, bytes.len());

    // The struct has a single Vec<String> field, so the serialized output should
    // start with a msgpack array format tag (fixarray=0x90..=0x9f, array16=0xdc,
    // array32=0xdd), NOT a binary format tag.
    let tag = bytes[0];
    assert!(
        matches!(tag, 0x90..=0x9f | 0xdc | 0xdd),
        "expected array format tag, got 0x{:02x}",
        tag,
    );

    // Round-trip via slice and iterator.
    let (o, deserialized) = WithStrings::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(o, n);
    assert_eq!(val, deserialized);

    let (p, deserialized_iter) = WithStrings::unpack_iter(bytes).unwrap();
    assert_eq!(p, n);
    assert_eq!(val, deserialized_iter);
}

#[test]
fn empty_vec() {
    let v: Vec<u8> = vec![];
    let mut bytes = vec![];
    let n = v.pack(&mut bytes);
    let (o, x) = <Vec<u8> as Unpackable>::unpack_with_ofs(&bytes).unwrap();
    let (p, y) = <Vec<u8> as Unpackable>::unpack_iter(bytes).unwrap();
    assert_eq!(o, n);
    assert_eq!(p, n);
    assert_eq!(v, x);
    assert_eq!(v, y);
}

#[test]
fn empty_str() {
    let s = "";
    let mut bytes = vec![];
    let n = s.pack(&mut bytes);
    let (o, x) = String::unpack_with_ofs(&bytes).unwrap();
    let (p, y) = String::unpack_iter(bytes).unwrap();
    assert_eq!(o, n);
    assert_eq!(p, n);
    assert_eq!(s, x);
    assert_eq!(s, y);
}

proptest! {
    #[test]
    fn vec(v: Vec<u8>) {
        utils::case(v);
    }

    #[test]
    fn str(s: String) {
        utils::case(s);
    }

    #[test]
    #[ignore]
    fn large_vec(v in prop::collection::vec(any::<u8>(), 0..=u16::MAX as usize * 2)) {
        utils::case(v);
    }

    #[test]
    #[ignore]
    fn large_str(v in prop::collection::vec(any::<char>(), 0..=u16::MAX as usize * 2)) {
        utils::case(v.into_iter().collect::<String>());
    }
}

// ── #[msgpacker(binary)] annotation ─────────────────────────────────────────

#[test]
fn binary_attr_named_field() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    struct Wrap {
        #[msgpacker(binary)]
        data: Vec<u8>,
    }

    let val = Wrap {
        data: vec![0xde, 0xad, 0xbe, 0xef],
    };

    let bytes = val.pack_to_vec();

    // Must use binary format tag, not array.
    assert!(
        matches!(bytes[0], 0xc4 | 0xc5 | 0xc6),
        "expected BIN tag, got 0x{:02x}",
        bytes[0]
    );
    assert_eq!(bytes[0], 0xc4);
    assert_eq!(bytes[1], 4);
    assert_eq!(&bytes[2..6], &[0xde, 0xad, 0xbe, 0xef]);

    let (n, got) = Wrap::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(val, got);

    let (n, got_iter) = Wrap::unpack_iter(bytes).unwrap();
    assert_eq!(n, got_iter.data.len() + 2); // BIN8 header (2) + payload
    assert_eq!(val, got_iter);
}

#[test]
fn binary_attr_tuple_field() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    struct Wrap(#[msgpacker(binary)] Vec<u8>);

    let val = Wrap(vec![1, 2, 3]);
    let bytes = val.pack_to_vec();
    assert!(matches!(bytes[0], 0xc4 | 0xc5 | 0xc6));

    let (n, got) = Wrap::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(val, got);

    let (n, got_iter) = Wrap::unpack_iter(bytes).unwrap();
    assert_eq!(n, got.0.len() + 2);
    assert_eq!(val, got_iter);
}

#[test]
fn binary_attr_enum_variant() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    enum Msg {
        Raw {
            #[msgpacker(binary)]
            payload: Vec<u8>,
        },
        Text(String),
    }

    let val = Msg::Raw {
        payload: vec![0x01, 0x02, 0x03],
    };
    let bytes = val.pack_to_vec();
    // discriminant 0 is packed as fixint (1 byte), then BIN8 payload
    assert!(matches!(bytes[1], 0xc4 | 0xc5 | 0xc6));

    let (n, got) = Msg::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(val, got);

    let (n2, got_iter) = Msg::unpack_iter(bytes).unwrap();
    assert_eq!(n, n2);
    assert_eq!(val, got_iter);
}

#[test]
fn binary_attr_option_vec_u8() {
    #[derive(Debug, PartialEq, Eq, MsgPacker)]
    struct Wrap {
        #[msgpacker(binary)]
        data: Option<Vec<u8>>,
    }

    // Some case: must use binary format, not array.
    let some = Wrap {
        data: Some(vec![0x01, 0x02, 0x03]),
    };
    let bytes = some.pack_to_vec();
    assert!(
        matches!(bytes[0], 0xc4 | 0xc5 | 0xc6),
        "Some should produce BIN tag, got 0x{:02x}",
        bytes[0]
    );
    let (n, got) = Wrap::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(some, got);
    let (_, got_iter) = Wrap::unpack_iter(bytes).unwrap();
    assert_eq!(some, got_iter);

    // None case: must use nil (0xc0).
    let none = Wrap { data: None };
    let bytes = none.pack_to_vec();
    assert_eq!(bytes[0], 0xc0, "None should produce nil");
    let (n, got) = Wrap::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(none, got);
    let (_, got_iter) = Wrap::unpack_iter(bytes).unwrap();
    assert_eq!(none, got_iter);
}

#[test]
fn binary_attr_borrowed() {
    #[derive(Debug, PartialEq, MsgPackerBorrowed)]
    struct Wrap {
        #[msgpacker(binary)]
        data: Vec<u8>,
    }

    let val = Wrap {
        data: vec![0xaa, 0xbb],
    };
    let bytes = val.pack_to_vec();
    assert!(matches!(bytes[0], 0xc4 | 0xc5 | 0xc6));

    let (n, got) = Wrap::unpack_with_ofs(&bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(val, got);
}
