use core::convert::Infallible;

use facet::Facet;
use facet_core::Opaque;
use facet_reflect::Peek;
use facet_testhelpers::test;

// Regression test for https://github.com/facet-rs/facet/issues/998
// Option<Enum> was incorrectly reporting the active variant due to
// niche optimization handling in RustNPO.
#[derive(Facet)]
#[repr(u8)]
enum SimpleEnum {
    Variant,
}

#[test]
fn option_of_enum_niche_optimization() {
    // Test None case - this was incorrectly returning "Some" before the fix
    let none_value: Option<SimpleEnum> = None;
    let peek = Peek::new(&none_value);
    let peek_enum = peek.into_enum().unwrap();
    assert_eq!(
        peek_enum.variant_name_active().unwrap(),
        "None",
        "Option::None was incorrectly identified as Some"
    );

    // Test Some case
    let some_value: Option<SimpleEnum> = Some(SimpleEnum::Variant);
    let peek = Peek::new(&some_value);
    let peek_enum = peek.into_enum().unwrap();
    assert_eq!(
        peek_enum.variant_name_active().unwrap(),
        "Some",
        "Option::Some was incorrectly identified"
    );
}

#[derive(Facet)]
#[repr(u8)]
enum DefinitelyNotAnEnum {
    #[allow(dead_code)]
    Some(u32),
    None,
}

#[test]
fn peek_enum() {
    // Test with Some value
    let some_value = DefinitelyNotAnEnum::Some(42);
    let peek_value = Peek::new(&some_value);

    // Convert to enum and check we can convert to PeekEnum
    let peek_enum = peek_value.into_enum().unwrap();
    let peek_def_not_enum = peek_enum;

    assert!(peek_def_not_enum.variant_name_active().unwrap() == "Some");

    // Check if it's the Some variant
    if peek_def_not_enum.variant_name_active().unwrap() == "Some" {
        // Get the value field using the field method with index
        let inner_value = peek_def_not_enum.field(0).unwrap().unwrap();
        let value = *inner_value.get::<u32>().unwrap();
        assert_eq!(value, 42);
    } else {
        panic!("Expected Some variant");
    }

    // Test with None value
    let none_value = DefinitelyNotAnEnum::None;
    let peek_value = Peek::new(&none_value);

    // Convert to enum and check we can convert to PeekEnum
    let peek_enum = peek_value.into_enum().unwrap();
    let peek_def_not_enum = peek_enum;

    assert!(peek_def_not_enum.variant_name_active().unwrap() == "None");
    // None variant has no fields to check
}

#[derive(Facet)]
#[repr(C)]
#[allow(dead_code)]
enum ReprCEnum {
    Unit,
    Tuple(u32),
    Struct { a: u8, b: String },
}

#[test]
fn peek_repr_c_enum() {
    // Test with unit
    let unit_value = ReprCEnum::Unit;
    let peek_value = Peek::new(&unit_value);

    // Convert to enum and check we can convert to PeekEnum
    let peek_enum = peek_value.into_enum().unwrap();
    assert!(peek_enum.variant_name_active().unwrap() == "Unit");

    // Test with tuple
    let unit_value = ReprCEnum::Tuple(42);
    let peek_value = Peek::new(&unit_value);

    // Convert to enum and check we can convert to PeekEnum
    let peek_enum = peek_value.into_enum().unwrap();

    assert!(peek_enum.variant_name_active().unwrap() == "Tuple");
    // Get the value field using the field method with index
    let inner_value = peek_enum.field(0).unwrap().unwrap();
    let value = *inner_value.get::<u32>().unwrap();
    assert_eq!(value, 42);

    // Test with struct
    let unit_value = ReprCEnum::Struct {
        a: 42,
        b: "Hello".to_string(),
    };
    let peek_value = Peek::new(&unit_value);
    // Convert to enum and check we can convert to PeekEnum
    let peek_enum = peek_value.into_enum().unwrap();
    assert!(peek_enum.variant_name_active().unwrap() == "Struct");
    // Get the value field using the field method with index
    let inner_value = peek_enum.field(0).unwrap().unwrap();
    let value = *inner_value.get::<u8>().unwrap();
    assert_eq!(value, 42);
    // Get the value field using the field method with name
    let inner_value = peek_enum.field_by_name("b").unwrap().unwrap();
    let value = inner_value.get::<String>().unwrap();
    assert_eq!(value, "Hello");
}

// Regression test for https://github.com/facet-rs/facet/issues/998#issuecomment-3605191431
// Option<Opaque<Infallible>> is a zero-size type, and calling discriminant() on it
// would read junk from memory, causing a SEGFAULT.
#[test]
fn option_of_zst_no_segfault() {
    // Option<Opaque<Infallible>> has size 0
    assert_eq!(
        core::mem::size_of::<Option<Opaque<Infallible>>>(),
        0,
        "Expected Option<Opaque<Infallible>> to be a ZST"
    );

    // Test None case - this should not crash even though it's a ZST
    let none_value: Option<Opaque<Infallible>> = None;
    let peek = Peek::new(&none_value);
    let peek_enum = peek.into_enum().unwrap();

    // This should not cause a SEGFAULT - discriminant() was reading junk memory
    assert_eq!(
        peek_enum.variant_name_active().unwrap(),
        "None",
        "Option::None was incorrectly identified"
    );

    // Also test calling discriminant() directly - this used to cause SEGFAULT
    // by reading memory for a type that has size 0
    let _ = peek_enum.discriminant();
}
