use msgpacker::{MsgPacker, MsgPackerBorrowed, Packable, UnpackableBorrowed};

// Helper: pack `x`, then unpack via `UnpackableBorrowed` and assert equality.
fn case_borrowed<'a, T>(bytes: &'a [u8], expected: &T)
where
    T: UnpackableBorrowed<'a> + PartialEq + core::fmt::Debug,
    <T as UnpackableBorrowed<'a>>::Error: core::fmt::Debug,
{
    let (n, got) = T::unpack_with_ofs(bytes).unwrap();
    assert_eq!(n, bytes.len());
    assert_eq!(&got, expected);
}

// ── struct with owned fields (no lifetime param, '__ a is introduced) ────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct Owned {
    x: u32,
    y: u64,
}

#[test]
fn owned_fields() {
    let v = Owned { x: 42, y: 1337 };
    let bytes = v.pack_to_vec();
    case_borrowed::<Owned>(&bytes, &v);
}

// ── struct with borrowed fields (lifetime param reused) ──────────────────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct BorrowedStr<'a> {
    name: &'a str,
    tag: &'a str,
}

#[test]
fn borrowed_str_fields() {
    // pack a compatible owned type, then unpack into the borrowed view
    #[derive(MsgPacker)]
    struct OwnedStr {
        name: String,
        tag: String,
    }

    let src = OwnedStr {
        name: "hello".into(),
        tag: "world".into(),
    };
    let bytes = src.pack_to_vec();

    let expected = BorrowedStr {
        name: "hello",
        tag: "world",
    };
    case_borrowed::<BorrowedStr<'_>>(&bytes, &expected);
}

// ── struct with mixed borrowed/owned fields ───────────────────────────────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct Mixed<'a> {
    id: u64,
    label: &'a str,
}

#[test]
fn mixed_fields() {
    #[derive(MsgPacker)]
    struct MixedOwned {
        id: u64,
        label: String,
    }

    let src = MixedOwned {
        id: 99,
        label: "test".into(),
    };
    let bytes = src.pack_to_vec();

    let expected = Mixed {
        id: 99,
        label: "test",
    };
    case_borrowed::<Mixed<'_>>(&bytes, &expected);
}

// ── tuple struct (unnamed fields) ────────────────────────────────────────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct Pair(u32, u64);

#[test]
fn tuple_struct() {
    let v = Pair(7, 13);
    let bytes = v.pack_to_vec();
    case_borrowed::<Pair>(&bytes, &v);
}

// ── unit struct ──────────────────────────────────────────────────────────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct Unit;

#[test]
fn unit_struct() {
    let bytes = Unit.pack_to_vec();
    case_borrowed::<Unit>(&bytes, &Unit);
}

// ── enum ─────────────────────────────────────────────────────────────────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
enum Color {
    Red,
    Green,
    Blue,
    Custom(u8, u8, u8),
    Named { r: u8, g: u8, b: u8 },
}

#[test]
fn enum_unit_variants() {
    for v in [Color::Red, Color::Green, Color::Blue] {
        let bytes = v.pack_to_vec();
        case_borrowed::<Color>(&bytes, &v);
    }
}

#[test]
fn enum_tuple_variant() {
    let v = Color::Custom(10, 20, 30);
    let bytes = v.pack_to_vec();
    case_borrowed::<Color>(&bytes, &v);
}

#[test]
fn enum_named_variant() {
    let v = Color::Named { r: 1, g: 2, b: 3 };
    let bytes = v.pack_to_vec();
    case_borrowed::<Color>(&bytes, &v);
}

// ── Option<&'a T> field (regression: ?Sized bound on Packable for &X) ─────────

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct MaybeStr<'a> {
    value: Option<&'a str>,
}

#[test]
fn option_borrowed_str() {
    #[derive(MsgPacker)]
    struct MaybeStrOwned {
        value: Option<String>,
    }

    let some = MaybeStrOwned {
        value: Some("hello".into()),
    };
    let bytes = some.pack_to_vec();
    let expected = MaybeStr {
        value: Some("hello"),
    };
    case_borrowed::<MaybeStr<'_>>(&bytes, &expected);

    let none = MaybeStrOwned { value: None };
    let bytes = none.pack_to_vec();
    let expected = MaybeStr { value: None };
    case_borrowed::<MaybeStr<'_>>(&bytes, &expected);
}

// ── Vec<&'a T> field (regression: is_vec_of_non_u8 + UnpackableBorrowed for Vec) ──

#[derive(Debug, PartialEq, MsgPackerBorrowed)]
struct StrSlice<'a> {
    items: Vec<&'a str>,
}

#[test]
fn vec_borrowed_str() {
    #[derive(MsgPacker)]
    struct StrSliceOwned {
        items: Vec<String>,
    }

    let src = StrSliceOwned {
        items: vec!["a".into(), "bb".into(), "ccc".into()],
    };
    let bytes = src.pack_to_vec();
    let expected = StrSlice {
        items: vec!["a", "bb", "ccc"],
    };
    case_borrowed::<StrSlice<'_>>(&bytes, &expected);
}
