//! PerCpu 测试
#![allow(unsafe_code)]

use std::{
    alloc::Layout,
    ptr::NonNull,
    sync::{
        Arc,
        atomic::{AtomicPtr, AtomicU64, AtomicUsize, Ordering},
    },
};

use moa_percpu::{Global, PerCpu, PerCpuAlloc};

// ══════════════════════════════════════════
//  动态定义（new / new_in）
// ══════════════════════════════════════════

#[test]
fn new_basic() {
    let pc = PerCpu::new(4, || 0u64);
    assert_eq!(pc.nr_cpus(), 4);
    for i in 0..4 {
        assert_eq!(*pc.cpu(i), 0);
    }
}

#[test]
fn new_single_cpu() {
    let pc = PerCpu::new(1, || 42u32);
    assert_eq!(pc.nr_cpus(), 1);
    assert_eq!(*pc.cpu(0), 42);
}

#[test]
fn new_closure_per_cpu_value() {
    let mut idx = 0usize;
    let pc = PerCpu::new(8, || {
        let v = idx;
        idx += 1;
        v
    });
    for i in 0..8 {
        assert_eq!(*pc.cpu(i), i);
    }
}

#[test]
fn cpu_mut_write_read() {
    let pc = PerCpu::new(4, || 0u64);
    unsafe { *pc.cpu_mut(0) = 10 };
    unsafe { *pc.cpu_mut(3) = 30 };
    assert_eq!(*pc.cpu(0), 10);
    assert_eq!(*pc.cpu(1), 0);
    assert_eq!(*pc.cpu(2), 0);
    assert_eq!(*pc.cpu(3), 30);
}

#[test]
fn cpu_mut_no_cross_slot_interference() {
    let pc = PerCpu::new(4, || 0u64);
    for i in 0..4 {
        unsafe { *pc.cpu_mut(i) = (i * 100) as u64 };
    }
    for i in 0..4 {
        assert_eq!(*pc.cpu(i), (i * 100) as u64);
    }
}

// ══════════════════════════════════════════
//  非 Copy 类型
// ══════════════════════════════════════════

#[test]
fn new_with_string() {
    let pc = PerCpu::new(3, || String::from("hello"));
    assert_eq!(pc.cpu(0).as_str(), "hello");
    assert_eq!(pc.cpu(2).as_str(), "hello");
    unsafe { pc.cpu_mut(1).push_str(" world") };
    assert_eq!(pc.cpu(1).as_str(), "hello world");
    assert_eq!(pc.cpu(0).as_str(), "hello");
}

#[test]
fn new_with_vec() {
    let pc = PerCpu::new(2, Vec::<u32>::new);
    unsafe { pc.cpu_mut(0).push(1) };
    unsafe { pc.cpu_mut(0).push(2) };
    unsafe { pc.cpu_mut(1).push(99) };
    assert_eq!(pc.cpu(0).as_slice(), &[1, 2]);
    assert_eq!(pc.cpu(1).as_slice(), &[99]);
}

// ══════════════════════════════════════════
//  ZST 类型
// ══════════════════════════════════════════

#[test]
fn new_with_zst() {
    #[derive(Debug, PartialEq)]
    struct Marker;
    let pc = PerCpu::new(4, || Marker);
    assert_eq!(*pc.cpu(0), Marker);
    assert_eq!(*pc.cpu(3), Marker);
}

// ══════════════════════════════════════════
//  Drop
// ══════════════════════════════════════════

#[test]
fn drop_runs_for_all_slots() {
    let counter = Arc::new(AtomicU64::new(0));
    struct Guard(Arc<AtomicU64>);
    impl Drop for Guard {
        fn drop(&mut self) {
            self.0.fetch_add(1, Ordering::Relaxed);
        }
    }
    {
        let _pc = PerCpu::new(5, || Guard(counter.clone()));
    }
    assert_eq!(counter.load(Ordering::Relaxed), 5);
}

#[test]
fn drop_uninit_is_noop() {
    // uninit 的 PerCpu drop 不应 panic 或 double-free
    {
        let _pc = PerCpu::<u64>::uninit();
    }
}

// ══════════════════════════════════════════
//  平坦布局（as_ptr）
// ══════════════════════════════════════════

#[test]
fn as_ptr_contiguous() {
    let pc = PerCpu::new(4, || 0xdead_u32);
    let ptr = pc.as_ptr();
    for i in 0..4 {
        assert_eq!(unsafe { *ptr.add(i) }, 0xdead);
    }
}

#[test]
fn as_ptr_stride_matches_size_of() {
    let pc = PerCpu::new(3, || [0u8; 64]);
    let ptr = pc.as_ptr();
    let stride = unsafe { ptr.add(1) as usize - ptr as usize };
    assert_eq!(stride, size_of::<[u8; 64]>());
}

// ══════════════════════════════════════════
//  repr(C) 布局验证
// ══════════════════════════════════════════

#[test]
fn repr_c_ptr_at_offset_zero() {
    static G: PerCpu<u64> = PerCpu::uninit();
    unsafe { G.init(1, || 77) };
    let struct_addr = &G as *const _ as *const AtomicPtr<u64>;
    let loaded = unsafe { &*struct_addr }.load(Ordering::Relaxed);
    assert_eq!(unsafe { *loaded }, 77);
}

#[test]
fn repr_c_nr_cpus_at_offset_8() {
    static G: PerCpu<u32> = PerCpu::uninit();
    unsafe { G.init(3, || 0) };
    let struct_addr = &G as *const _ as usize;
    let nr_cpus_ptr = (struct_addr + size_of::<AtomicPtr<u32>>()) as *const AtomicUsize;
    let nr = unsafe { &*nr_cpus_ptr }.load(Ordering::Relaxed);
    assert_eq!(nr, 3);
}

// ══════════════════════════════════════════
//  全局定义（uninit + init）
// ══════════════════════════════════════════

#[test]
fn global_init_and_access() {
    static G: PerCpu<u64> = PerCpu::uninit();
    unsafe { G.init(4, || 0) };
    assert_eq!(G.nr_cpus(), 4);
    assert_eq!(*G.cpu(0), 0);
    unsafe { *G.cpu_mut(2) = 99 };
    assert_eq!(*G.cpu(2), 99);
}

#[test]
fn global_as_ptr_not_null() {
    static G: PerCpu<u32> = PerCpu::uninit();
    unsafe { G.init(2, || 0xbeef) };
    let ptr = G.as_ptr();
    assert!(!ptr.is_null());
    for i in 0..2 {
        assert_eq!(unsafe { *ptr.add(i) }, 0xbeef);
    }
}

#[test]
fn global_uninit_nr_cpus_is_zero() {
    static G: PerCpu<u64> = PerCpu::uninit();
    assert_eq!(G.nr_cpus(), 0);
    // init 后变为实际值
    unsafe { G.init(2, || 0) };
    assert_eq!(G.nr_cpus(), 2);
}

#[test]
fn global_multiple_instances_independent() {
    static A: PerCpu<u64> = PerCpu::uninit();
    static B: PerCpu<u64> = PerCpu::uninit();
    unsafe { A.init(2, || 1) };
    unsafe { B.init(3, || 2) };
    assert_eq!(A.nr_cpus(), 2);
    assert_eq!(B.nr_cpus(), 3);
    assert_eq!(*A.cpu(0), 1);
    assert_eq!(*B.cpu(0), 2);
    unsafe { *A.cpu_mut(0) = 100 };
    assert_eq!(*A.cpu(0), 100);
    assert_eq!(*B.cpu(0), 2);
}

#[test]
fn global_with_non_copy_type() {
    static G: PerCpu<AtomicU64> = PerCpu::uninit();
    unsafe { G.init(2, || AtomicU64::new(0)) };
    G.cpu(0).store(42, Ordering::Relaxed);
    G.cpu(1).store(99, Ordering::Relaxed);
    assert_eq!(G.cpu(0).load(Ordering::Relaxed), 42);
    assert_eq!(G.cpu(1).load(Ordering::Relaxed), 99);
}

// ══════════════════════════════════════════
//  自定义分配器
// ══════════════════════════════════════════

struct TrackingAlloc {
    alloc_count: AtomicUsize,
    dealloc_count: AtomicUsize,
}

impl TrackingAlloc {
    const fn new() -> Self {
        Self { alloc_count: AtomicUsize::new(0), dealloc_count: AtomicUsize::new(0) }
    }
}

// Safety: 转发到 Global，仅增加计数
unsafe impl PerCpuAlloc for &TrackingAlloc {
    fn allocate(&self, layout: Layout) -> NonNull<u8> {
        self.alloc_count.fetch_add(1, Ordering::Relaxed);
        Global.allocate(layout)
    }

    unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
        self.dealloc_count.fetch_add(1, Ordering::Relaxed);
        unsafe { Global.deallocate(ptr, layout) };
    }
}

#[test]
fn new_in_custom_alloc() {
    let tracker = TrackingAlloc::new();
    {
        let pc = PerCpu::new_in(4, || 0u64, &tracker);
        assert_eq!(pc.nr_cpus(), 4);
        assert_eq!(*pc.cpu(0), 0);
        assert_eq!(tracker.alloc_count.load(Ordering::Relaxed), 1);
    }
    assert_eq!(tracker.dealloc_count.load(Ordering::Relaxed), 1);
}

#[test]
fn init_in_custom_alloc() {
    static G: PerCpu<u64, &TrackingAlloc> = PerCpu::uninit();
    static TRACKER: TrackingAlloc = TrackingAlloc::new();
    unsafe { G.init_in(2, || 0, &TRACKER) };
    assert_eq!(*G.cpu(0), 0);
    assert_eq!(TRACKER.alloc_count.load(Ordering::Relaxed), 1);
}

// ══════════════════════════════════════════
//  边界条件
// ══════════════════════════════════════════

#[test]
#[should_panic(expected = "nr_cpus must be > 0")]
fn new_zero_cpus_panics() {
    let _ = PerCpu::new(0, || 0u64);
}

#[test]
#[should_panic(expected = "nr_cpus must be > 0")]
fn init_zero_cpus_panics() {
    static G: PerCpu<u64> = PerCpu::uninit();
    unsafe { G.init(0, || 0) };
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "cpu index out of range")]
fn cpu_oob_panics() {
    let pc = PerCpu::new(2, || 0u64);
    let _ = pc.cpu(2);
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "cpu index out of range")]
fn cpu_mut_oob_panics() {
    let pc = PerCpu::new(2, || 0u64);
    unsafe {
        let _ = pc.cpu_mut(2);
    }
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "PerCpu already initialized")]
fn double_init_panics() {
    static G: PerCpu<u64> = PerCpu::uninit();
    unsafe { G.init(1, || 0) };
    unsafe { G.init(1, || 0) };
}

#[test]
#[cfg(debug_assertions)]
#[should_panic(expected = "cpu index out of range")]
fn access_before_init_panics() {
    static G: PerCpu<u64> = PerCpu::uninit();
    let _ = G.cpu(0);
}
