use ax_codec_core::buffer::{SliceReader, VecWriter};
use ax_codec_core::{Decode, Encode, Validate, View};
use ax_codec_derive::{Decode, Encode, View};

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
struct NamedStruct {
    id: u32,
    name: String,
    active: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
struct UnnamedStruct(u16, u32);

#[derive(Debug, Clone, PartialEq, Eq, Encode, View)]
struct SimpleViewStruct {
    id: u32,
    active: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, View)]
enum SimpleEnum {
    A,
    B(u32),
    C { x: u16, y: u16 },
}

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
struct WithSkip {
    id: u32,
    #[ax_codec(skip)]
    cached: String,
}

impl Default for WithSkip {
    fn default() -> Self {
        Self {
            id: 0,
            cached: String::new(),
        }
    }
}

fn roundtrip_encode_decode<T: Encode + Decode + PartialEq + std::fmt::Debug>(val: T) {
    let mut w = VecWriter::new();
    val.encode(&mut w).unwrap();
    let mut r = SliceReader::new(w.as_slice());
    let decoded = T::decode(&mut r).unwrap();
    assert_eq!(val, decoded);
}

#[test]
fn named_struct_roundtrip() {
    roundtrip_encode_decode(NamedStruct {
        id: 42,
        name: "hello".into(),
        active: true,
    });
}

#[test]
fn unnamed_struct_roundtrip() {
    roundtrip_encode_decode(UnnamedStruct(0x1234, 0xDEADBEEF));
}

#[test]
fn view_struct_roundtrip() {
    let mut w = VecWriter::new();
    let val = SimpleViewStruct {
        id: 99,
        active: true,
    };
    val.encode(&mut w).unwrap();
    let mut r = SliceReader::new(w.as_slice());
    let decoded: SimpleViewStruct = View::view(&mut r).unwrap();
    assert_eq!(decoded.id, 99);
    assert_eq!(decoded.active, true);
}

#[test]
fn enum_roundtrip() {
    roundtrip_encode_decode(SimpleEnum::A);
    roundtrip_encode_decode(SimpleEnum::B(42));
    roundtrip_encode_decode(SimpleEnum::C { x: 1, y: 2 });
}

#[test]
fn skip_attribute_roundtrip() {
    let val = WithSkip {
        id: 42,
        cached: "should be ignored".into(),
    };
    let mut w = VecWriter::new();
    val.encode(&mut w).unwrap();
    let mut r = SliceReader::new(w.as_slice());
    let decoded = WithSkip::decode(&mut r).unwrap();
    assert_eq!(val.id, decoded.id);
    assert_eq!(decoded.cached, "");
}

#[test]
fn validate_struct_ok() {
    let mut w = VecWriter::new();
    let val = SimpleViewStruct {
        id: 99,
        active: true,
    };
    val.encode(&mut w).unwrap();
    let mut r = SliceReader::new(w.as_slice());
    SimpleViewStruct::validate(&mut r).unwrap();
}

#[test]
fn validate_struct_truncated_fails() {
    let mut w = VecWriter::new();
    let val = SimpleViewStruct {
        id: 99,
        active: true,
    };
    val.encode(&mut w).unwrap();
    let mut r = SliceReader::new(&w.as_slice()[..2]);
    assert!(SimpleViewStruct::validate(&mut r).is_err());
}

#[test]
fn validate_enum_ok() {
    let mut w = VecWriter::new();
    SimpleEnum::B(42).encode(&mut w).unwrap();
    let mut r = SliceReader::new(w.as_slice());
    SimpleEnum::validate(&mut r).unwrap();
}

#[test]
fn validate_enum_bad_discriminant_fails() {
    let buf = [0xffu8];
    let mut r = SliceReader::new(&buf);
    assert!(SimpleEnum::validate(&mut r).is_err());
}

#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
struct WithDefault {
    id: u32,
    #[ax_codec(default)]
    extra: String,
}

#[test]
fn default_attribute_uses_default_on_eof() {
    let mut w = VecWriter::new();
    42u32.encode(&mut w).unwrap();

    let mut r = SliceReader::new(w.as_slice());
    let decoded = WithDefault::decode(&mut r).unwrap();
    assert_eq!(decoded.id, 42);
    assert_eq!(decoded.extra, "");
}
