use crate::error::NativeError;
use crate::memory::{HeapArray, Memory, Value};
use std::collections::HashMap;
use std::os::raw::c_void;

/// JNI version constants
pub const JNI_VERSION_1_1: i32 = 0x00010001;
pub const JNI_VERSION_1_2: i32 = 0x00010002;
pub const JNI_VERSION_1_4: i32 = 0x00010004;
pub const JNI_VERSION_1_6: i32 = 0x00010006;
pub const JNI_VERSION_1_8: i32 = 0x00010008;

/// JNI Native Method Interface
#[repr(C)]
pub struct JNINativeInterface_ {
    // Reserved for future use
    reserved0: *mut c_void,
    reserved1: *mut c_void,
    reserved2: *mut c_void,
    reserved3: *mut c_void,

    // Method functions (simplified)
    get_version: *mut c_void,
    define_class: *mut c_void,
    find_class: *mut c_void,
    // ... more functions would be added in a complete implementation
}

/// JNI Native Method
pub type JNINativeMethod = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut *mut jvalue);

/// JNI Value union for different primitive types
#[repr(C)]
pub union jvalue {
    pub z: u8,          // boolean
    pub b: i8,          // byte
    pub c: u16,         // char
    pub s: i16,         // short
    pub i: i32,         // int
    pub j: i64,         // long
    pub f: f32,         // float
    pub d: f64,         // double
    pub l: *mut c_void, // object
}

/// Native method trait
pub trait NativeMethod: Send + Sync {
    /// Invoke the native method with the given arguments
    fn invoke(&self, args: &[Value], memory: &mut Memory) -> Result<Value, NativeError>;

    /// Get the signature of this method
    fn signature(&self) -> &str;

    /// Get the name of this method
    fn name(&self) -> &str;
}

/// Simple native method implementation using Rust closures
pub struct SimpleNativeMethod {
    name: String,
    signature: String,
    func: Box<dyn Fn(&[Value], &mut Memory) -> Result<Value, NativeError> + Send + Sync>,
}

impl SimpleNativeMethod {
    /// Create a new simple native method
    pub fn new<F>(name: String, signature: String, func: F) -> Self
    where
        F: Fn(&[Value], &mut Memory) -> Result<Value, NativeError> + Send + Sync + 'static,
    {
        Self {
            name,
            signature,
            func: Box::new(func),
        }
    }
}

impl NativeMethod for SimpleNativeMethod {
    fn invoke(&self, args: &[Value], memory: &mut Memory) -> Result<Value, NativeError> {
        (self.func)(args, memory)
    }

    fn signature(&self) -> &str {
        &self.signature
    }

    fn name(&self) -> &str {
        &self.name
    }
}

/// Registry for native methods
pub struct NativeRegistry {
    methods: HashMap<String, HashMap<String, Box<dyn NativeMethod>>>,
    // In a real implementation, we would have loaded libraries here
}

impl NativeRegistry {
    /// Create a new native registry
    pub fn new() -> Self {
        Self {
            methods: HashMap::new(),
        }
    }

    /// Register a native method
    pub fn register_method(
        &mut self,
        class_name: &str,
        method_name: &str,
        method: Box<dyn NativeMethod>,
    ) -> Result<(), NativeError> {
        self.methods
            .entry(class_name.to_string())
            .or_insert_with(HashMap::new)
            .insert(method_name.to_string(), method);
        Ok(())
    }

    /// Find a native method
    pub fn find_method(&self, class_name: &str, method_name: &str) -> Option<&dyn NativeMethod> {
        self.methods
            .get(class_name)?
            .get(method_name)
            .map(|m| m.as_ref())
    }

    /// Check if a method is native
    pub fn is_native(&self, class_name: &str, method_name: &str) -> bool {
        self.methods
            .get(class_name)
            .map_or(false, |methods| methods.contains_key(method_name))
    }
}

impl Default for NativeRegistry {
    fn default() -> Self {
        Self::new()
    }
}

/// Convert JVM Value to JNI jvalue
pub fn value_to_jvalue(value: Value) -> jvalue {
    match value {
        Value::Boolean(b) => jvalue { z: b as u8 },
        Value::Byte(b) => jvalue { b },
        Value::Char(c) => jvalue { c },
        Value::Short(s) => jvalue { s },
        Value::Int(i) => jvalue { i },
        Value::Long(l) => jvalue { j: l },
        Value::Float(f) => jvalue { f },
        Value::Double(d) => jvalue { d },
        Value::Reference(r) => jvalue {
            l: r as *mut c_void,
        },
        Value::ArrayRef(r) => jvalue {
            l: r as *mut c_void,
        },
        Value::Null => jvalue {
            l: std::ptr::null_mut(),
        },
        Value::ReturnAddress(_) => jvalue {
            l: std::ptr::null_mut(),
        },
    }
}

/// Convert JNI jvalue to JVM Value
pub fn jvalue_to_value(jvalue: jvalue) -> Value {
    unsafe {
        // Note: In a real implementation, we'd need type information
        // For now, we'll assume it's an int
        Value::Int(jvalue.i)
    }
}

/// Initialize built-in native methods
pub fn init_builtins(registry: &mut NativeRegistry) {
    // Register System.currentTimeMillis()
    registry
        .register_method(
            "java/lang/System",
            "currentTimeMillis",
            Box::new(SimpleNativeMethod::new(
                "currentTimeMillis".to_string(),
                "()J".to_string(),
                |_args: &[Value], _memory: &mut Memory| {
                    // Return current time in milliseconds
                    use std::time::{SystemTime, UNIX_EPOCH};
                    let duration = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| {
                        NativeError::NativeMethodFailed(
                            "System.currentTimeMillis".to_string(),
                            format!("Failed to get system time: {}", e),
                        )
                    })?;
                    Ok(Value::Long(duration.as_millis() as i64))
                },
            )),
        )
        .ok();

    // Register System.nanoTime()
    registry
        .register_method(
            "java/lang/System",
            "nanoTime",
            Box::new(SimpleNativeMethod::new(
                "nanoTime".to_string(),
                "()J".to_string(),
                |_args: &[Value], _memory: &mut Memory| {
                    // Return current time in nanoseconds
                    use std::time::{SystemTime, UNIX_EPOCH};
                    let duration = SystemTime::now().duration_since(UNIX_EPOCH).map_err(|e| {
                        NativeError::NativeMethodFailed(
                            "System.nanoTime".to_string(),
                            format!("Failed to get system time: {}", e),
                        )
                    })?;
                    Ok(Value::Long(duration.as_nanos() as i64))
                },
            )),
        )
        .ok();

    // Register Math.sqrt(double)
    registry
        .register_method(
            "java/lang/Math",
            "sqrt",
            Box::new(SimpleNativeMethod::new(
                "sqrt".to_string(),
                "(D)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    if args.len() != 1 {
                        return Err(NativeError::NativeMethodSignatureMismatch(
                            "Math.sqrt".to_string(),
                        ));
                    }

                    match args[0] {
                        Value::Double(d) => {
                            if d >= 0.0 {
                                Ok(Value::Double(d.sqrt()))
                            } else {
                                Ok(Value::Double(f64::NAN))
                            }
                        }
                        _ => Err(NativeError::NativeMethodSignatureMismatch(
                            "Math.sqrt expects a double argument".to_string(),
                        )),
                    }
                },
            )),
        )
        .ok();

    // Register Math.abs(int)
    registry
        .register_method(
            "java/lang/Math",
            "abs",
            Box::new(SimpleNativeMethod::new(
                "abs".to_string(),
                "(I)I".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Int(i)) => Ok(Value::Int(i.wrapping_abs())),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.abs(I)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.min(int, int)
    registry
        .register_method(
            "java/lang/Math",
            "min",
            Box::new(SimpleNativeMethod::new(
                "min".to_string(),
                "(II)I".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match (args.get(0), args.get(1)) {
                        (Some(Value::Int(a)), Some(Value::Int(b))) => Ok(Value::Int(*a.min(b))),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.min(II)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.max(int, int)
    registry
        .register_method(
            "java/lang/Math",
            "max",
            Box::new(SimpleNativeMethod::new(
                "max".to_string(),
                "(II)I".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match (args.get(0), args.get(1)) {
                        (Some(Value::Int(a)), Some(Value::Int(b))) => Ok(Value::Int(*a.max(b))),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.max(II)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.sin(double)
    registry
        .register_method(
            "java/lang/Math",
            "sin",
            Box::new(SimpleNativeMethod::new(
                "sin".to_string(),
                "(D)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Double(d)) => Ok(Value::Double(d.sin())),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.sin(D)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.cos(double)
    registry
        .register_method(
            "java/lang/Math",
            "cos",
            Box::new(SimpleNativeMethod::new(
                "cos".to_string(),
                "(D)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Double(d)) => Ok(Value::Double(d.cos())),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.cos(D)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.floor(double)
    registry
        .register_method(
            "java/lang/Math",
            "floor",
            Box::new(SimpleNativeMethod::new(
                "floor".to_string(),
                "(D)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Double(d)) => Ok(Value::Double(d.floor())),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.floor(D)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.ceil(double)
    registry
        .register_method(
            "java/lang/Math",
            "ceil",
            Box::new(SimpleNativeMethod::new(
                "ceil".to_string(),
                "(D)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Double(d)) => Ok(Value::Double(d.ceil())),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.ceil(D)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register Math.pow(double, double)
    registry
        .register_method(
            "java/lang/Math",
            "pow",
            Box::new(SimpleNativeMethod::new(
                "pow".to_string(),
                "(DD)D".to_string(),
                |args: &[Value], _memory: &mut Memory| {
                    match (args.get(0), args.get(1)) {
                        (Some(Value::Double(a)), Some(Value::Double(b))) => Ok(Value::Double(a.powf(*b))),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("Math.pow(DD)".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register String.length()
    registry
        .register_method(
            "java/lang/String",
            "length",
            Box::new(SimpleNativeMethod::new(
                "length".to_string(),
                "()I".to_string(),
                |args: &[Value], memory: &mut Memory| {
                    match args.first() {
                        Some(Value::Reference(addr)) => {
                            if let Some(obj) = memory.heap.get_object(*addr) {
                                if let Some(string_data) = obj.string_data.as_ref() {
                                    Ok(Value::Int(string_data.chars().count() as i32))
                                } else {
                                    Ok(Value::Int(0))
                                }
                            } else {
                                Ok(Value::Int(0))
                            }
                        }
                        Some(Value::Null) => Ok(Value::Int(0)),
                        _ => Err(NativeError::NativeMethodSignatureMismatch("String.length".to_string())),
                    }
                },
            )),
        )
        .ok();

    // Register String.hashCode()
    registry
        .register_method(
            "java/lang/String",
            "hashCode",
            Box::new(SimpleNativeMethod::new(
                "hashCode".to_string(),
                "()I".to_string(),
                |args: &[Value], memory: &mut Memory| {
                    if args.len() != 1 {
                        return Err(NativeError::NativeMethodSignatureMismatch(
                            "String.hashCode".to_string(),
                        ));
                    }

                    match &args[0] {
                        Value::Reference(addr) => {
                            if let Some(obj) = memory.heap.get_object(*addr) {
                                if let Some(string_data) = obj.string_data.as_ref() {
                                    // Simple hash code implementation
                                    let mut hash: i32 = 0;
                                    for c in string_data.chars() {
                                        hash = 31 * hash + (c as i32);
                                    }
                                    Ok(Value::Int(hash))
                                } else {
                                    // For non-string objects, use identity hash code
                                    Ok(Value::Int(*addr as i32))
                                }
                            } else {
                                Err(NativeError::NativeMethodFailed(
                                    "String.hashCode".to_string(),
                                    "Invalid object reference".to_string(),
                                ))
                            }
                        }
                        Value::Null => Ok(Value::Int(0)),
                        _ => Err(NativeError::NativeMethodSignatureMismatch(
                            "String.hashCode expects a string object".to_string(),
                        )),
                    }
                },
            )),
        )
        .ok();

    // Register System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
    registry
        .register_method(
            "java/lang/System",
            "arraycopy",
            Box::new(SimpleNativeMethod::new(
                "arraycopy".to_string(),
                "(Ljava/lang/Object;ILjava/lang/Object;II)V".to_string(),
                |args: &[Value], memory: &mut Memory| {
                    if args.len() != 5 {
                        return Err(NativeError::NativeMethodSignatureMismatch(
                            "System.arraycopy expects 5 args".to_string(),
                        ));
                    }
                    let src_arr = match &args[0] {
                        Value::Reference(addr) => *addr,
                        Value::Null => return Err(NativeError::NativeMethodFailed("System.arraycopy".to_string(), "source array is null".to_string())),
                        _ => return Err(NativeError::NativeMethodSignatureMismatch("System.arraycopy src".to_string())),
                    };
                    let src_pos = match args[1] { Value::Int(i) => i as usize, _ => return Err(NativeError::NativeMethodSignatureMismatch("System.arraycopy srcPos".to_string())) };
                    let dst_arr = match &args[2] {
                        Value::Reference(addr) => *addr,
                        Value::Null => return Err(NativeError::NativeMethodFailed("System.arraycopy".to_string(), "destination array is null".to_string())),
                        _ => return Err(NativeError::NativeMethodSignatureMismatch("System.arraycopy dest".to_string())),
                    };
                    let dst_pos = match args[3] { Value::Int(i) => i as usize, _ => return Err(NativeError::NativeMethodSignatureMismatch("System.arraycopy destPos".to_string())) };
                    let len = match args[4] { Value::Int(i) => i as usize, _ => return Err(NativeError::NativeMethodSignatureMismatch("System.arraycopy length".to_string())) };

                    {
                        let src_array = memory.heap.get_array(src_arr);
                        let dst_array = memory.heap.get_array(dst_arr);
                        let src_type = src_array.map(|a| std::mem::discriminant(a));
                        let dst_type = dst_array.map(|a| std::mem::discriminant(a));
                        if src_type != dst_type {
                            return Err(NativeError::NativeMethodFailed("System.arraycopy".to_string(), "array type mismatch".to_string()));
                        }
                    }

                    match memory.heap.get_array(src_arr) {
                        Some(HeapArray::IntArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<i32> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::IntArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::FloatArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<f32> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::FloatArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::LongArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<i64> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::LongArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::DoubleArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<f64> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::DoubleArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::ByteArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<u8> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::ByteArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::ShortArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<i16> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::ShortArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::CharArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<u16> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::CharArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::BooleanArray(s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<bool> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::BooleanArray(d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        Some(HeapArray::ReferenceArray(_, s)) => {
                            let copy_len = len.min(s.len().saturating_sub(src_pos));
                            let data: Vec<u32> = s[src_pos..src_pos + copy_len].to_vec();
                            if let Some(HeapArray::ReferenceArray(_, d)) = memory.heap.get_array_mut(dst_arr) {
                                let copy_len = copy_len.min(d.len().saturating_sub(dst_pos));
                                d[dst_pos..dst_pos + copy_len].copy_from_slice(&data[..copy_len]);
                            }
                        }
                        _ => return Err(NativeError::NativeMethodFailed("System.arraycopy".to_string(), "unsupported array type".to_string())),
                    }
                    Ok(Value::Null)
                },
            )),
        )
        .ok();
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_math_abs() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let native = registry.find_method("java/lang/Math", "abs").unwrap();
        assert_eq!(native.invoke(&[Value::Int(-5)], &mut Memory::new()).unwrap(), Value::Int(5));
        assert_eq!(native.invoke(&[Value::Int(5)], &mut Memory::new()).unwrap(), Value::Int(5));
        assert_eq!(native.invoke(&[Value::Int(i32::MIN)], &mut Memory::new()).unwrap(), Value::Int(i32::MIN));
    }

    #[test]
    fn test_math_min_max() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let min_fn = registry.find_method("java/lang/Math", "min").unwrap();
        assert_eq!(min_fn.invoke(&[Value::Int(3), Value::Int(7)], &mut Memory::new()).unwrap(), Value::Int(3));
        let max_fn = registry.find_method("java/lang/Math", "max").unwrap();
        assert_eq!(max_fn.invoke(&[Value::Int(3), Value::Int(7)], &mut Memory::new()).unwrap(), Value::Int(7));
    }

    #[test]
    fn test_math_sqrt() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let sqrt_fn = registry.find_method("java/lang/Math", "sqrt").unwrap();
        assert_eq!(sqrt_fn.invoke(&[Value::Double(4.0)], &mut Memory::new()).unwrap(), Value::Double(2.0));
    }

    #[test]
    fn test_math_sin_cos() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let sin_fn = registry.find_method("java/lang/Math", "sin").unwrap();
        let cos_fn = registry.find_method("java/lang/Math", "cos").unwrap();
        let sin_val = sin_fn.invoke(&[Value::Double(0.0)], &mut Memory::new()).unwrap();
        let cos_val = cos_fn.invoke(&[Value::Double(0.0)], &mut Memory::new()).unwrap();
        assert!(matches!(sin_val, Value::Double(d) if d.abs() < 1e-10));
        assert!(matches!(cos_val, Value::Double(d) if (d - 1.0).abs() < 1e-10));
    }

    #[test]
    fn test_math_floor_ceil() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let floor_fn = registry.find_method("java/lang/Math", "floor").unwrap();
        let ceil_fn = registry.find_method("java/lang/Math", "ceil").unwrap();
        assert_eq!(floor_fn.invoke(&[Value::Double(2.7)], &mut Memory::new()).unwrap(), Value::Double(2.0));
        assert_eq!(ceil_fn.invoke(&[Value::Double(2.1)], &mut Memory::new()).unwrap(), Value::Double(3.0));
    }

    #[test]
    fn test_math_pow() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let pow_fn = registry.find_method("java/lang/Math", "pow").unwrap();
        assert_eq!(pow_fn.invoke(&[Value::Double(2.0), Value::Double(3.0)], &mut Memory::new()).unwrap(), Value::Double(8.0));
    }

    #[test]
    fn test_string_length() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let len_fn = registry.find_method("java/lang/String", "length").unwrap();
        // Null string returns 0
        assert_eq!(len_fn.invoke(&[Value::Null], &mut Memory::new()).unwrap(), Value::Int(0));
    }

    #[test]
    fn test_string_hashcode() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let hash_fn = registry.find_method("java/lang/String", "hashCode").unwrap();
        // Null string returns 0
        assert_eq!(hash_fn.invoke(&[Value::Null], &mut Memory::new()).unwrap(), Value::Int(0));
    }

    #[test]
    fn test_system_arraycopy() {
        let mut registry = NativeRegistry::new();
        init_builtins(&mut registry);
        let copy_fn = registry.find_method("java/lang/System", "arraycopy").unwrap();

        let mut memory = Memory::new();
        let src = HeapArray::IntArray(vec![1, 2, 3, 4, 5]);
        let dst = HeapArray::IntArray(vec![0, 0, 0, 0, 0]);
        let src_addr = memory.heap.allocate_array(src);
        let dst_addr = memory.heap.allocate_array(dst);

        let result = copy_fn.invoke(&[
            Value::Reference(src_addr),
            Value::Int(1),
            Value::Reference(dst_addr),
            Value::Int(0),
            Value::Int(3),
        ], &mut memory);
        assert!(result.is_ok());

        if let Some(HeapArray::IntArray(arr)) = memory.heap.get_array(dst_addr) {
            assert_eq!(arr, &[2, 3, 4, 0, 0]);
        } else {
            panic!("expected IntArray");
        }
    }
}
