use super::*;
use crate::class_file::ClassFile;
use crate::memory::{StackFrame, Value};

fn create_interpreter() -> Interpreter {
    Interpreter::new()
}

fn create_minimal_class() -> ClassFile {
    ClassFile {
        magic: 0xCAFEBABE,
        minor_version: 0,
        major_version: 52,
        constant_pool: vec![],
        access_flags: 0x0021,
        this_class: 1,
        super_class: 2,
        interfaces: vec![],
        fields: vec![],
        methods: vec![],
        attributes: vec![],
    }
}

fn run_instruction(
    opcode: u8,
    operands: &[u8],
    stack: Vec<Value>,
    locals: Vec<Value>,
) -> (StackFrame, Result<bool, JvmError>) {
    let mut interp = create_interpreter();
    let class = create_minimal_class();
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.stack = stack;
    if !locals.is_empty() {
        for (i, val) in locals.into_iter().enumerate() {
            frame.locals[i] = val;
        }
    }

    let mut code = vec![opcode];
    code.extend_from_slice(operands);

    // Simulate the interpreter loop:
    // 1. Read opcode at pc (0)
    // 2. Increment pc (1)
    // 3. Dispatch
    frame.pc = 1;
    let result = interp.dispatch_instruction(&class, &code, &mut frame, opcode);
    (frame, result)
}

#[test]
fn test_stack_pop2() {
    // pop2: Pop the top one or two values from the operand stack.
    // Form 1: value2, value1 -> ... (where each is category 1)
    let (frame, res) = run_instruction(0x58, &[], vec![Value::Int(1), Value::Int(2)], vec![]);
    assert!(res.is_ok());
    assert!(frame.stack.is_empty());

    // Form 2: value -> ... (where value is category 2)
    let (frame, res) = run_instruction(0x58, &[], vec![Value::Long(123)], vec![]);
    assert!(res.is_ok());
    assert!(frame.stack.is_empty());
}

#[test]
fn test_stack_dup2_x2() {
    // dup2_x2: Duplicate the top one or two values and insert two, three, or four values down

    // Form 1: val1 (cat1), val2 (cat1) on top. val3 (cat1), val4 (cat1) below.
    // Stack: ..., val4, val3, val2, val1 -> ..., val2, val1, val4, val3, val2, val1
    let initial_stack = vec![
        Value::Int(4), // val4
        Value::Int(3), // val3
        Value::Int(2), // val2
        Value::Int(1), // val1
    ];
    let (frame, res) = run_instruction(0x5e, &[], initial_stack, vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack.len(), 6);
    assert_eq!(frame.stack[0], Value::Int(2)); // inserted
    assert_eq!(frame.stack[1], Value::Int(1)); // inserted
    assert_eq!(frame.stack[2], Value::Int(4));
    assert_eq!(frame.stack[3], Value::Int(3));
    assert_eq!(frame.stack[4], Value::Int(2));
    assert_eq!(frame.stack[5], Value::Int(1));

    // Form 2: val1 (cat2) on top. val2 (cat1), val3 (cat1) below.
    // Stack: ..., val3, val2, val1 -> ..., val1, val3, val2, val1
    let initial_stack = vec![
        Value::Int(3),  // val3
        Value::Int(2),  // val2
        Value::Long(1), // val1
    ];
    let (frame, res) = run_instruction(0x5e, &[], initial_stack, vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack.len(), 4);
    assert_eq!(frame.stack[0], Value::Long(1)); // inserted
    assert_eq!(frame.stack[1], Value::Int(3));
    assert_eq!(frame.stack[2], Value::Int(2));
    assert_eq!(frame.stack[3], Value::Long(1));

    // Form 3: val1 (cat1), val2 (cat1) on top. val3 (cat2) below.
    // Stack: ..., val3, val2, val1 -> ..., val2, val1, val3, val2, val1
    let initial_stack = vec![
        Value::Long(3), // val3
        Value::Int(2),  // val2
        Value::Int(1),  // val1
    ];
    let (frame, res) = run_instruction(0x5e, &[], initial_stack, vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack.len(), 5);
    assert_eq!(frame.stack[0], Value::Int(2)); // inserted
    assert_eq!(frame.stack[1], Value::Int(1)); // inserted
    assert_eq!(frame.stack[2], Value::Long(3));
    assert_eq!(frame.stack[3], Value::Int(2));
    assert_eq!(frame.stack[4], Value::Int(1));

    // Form 4: val1 (cat2) on top. val2 (cat2) below.
    // Stack: ..., val2, val1 -> ..., val1, val2, val1
    let initial_stack = vec![
        Value::Long(2), // val2
        Value::Long(1), // val1
    ];
    let (frame, res) = run_instruction(0x5e, &[], initial_stack, vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack.len(), 3);
    assert_eq!(frame.stack[0], Value::Long(1)); // inserted
    assert_eq!(frame.stack[1], Value::Long(2));
    assert_eq!(frame.stack[2], Value::Long(1));
}

#[test]
fn test_control_flow_if_acmpeq() {
    // if_acmpeq: Pop ref2, ref1. Jump if ref1 == ref2.
    // 0xa5
    // Equal references (same object/array or both null)
    let (mut frame, res) =
        run_instruction(0xa5, &[0x00, 0x05], vec![Value::Null, Value::Null], vec![]);
    assert!(res.is_ok());
    // Should jump. Offset is 5. Base is 0. Target is 0 + 5 = 5.
    // But verify: frame.pc starts at 1. Offset read at 1 is 0x0005.
    // The jump is relative to the opcode address (0).
    // So new pc should be 5.
    assert_eq!(frame.pc, 5);

    // Unequal references (null vs non-null)
    // We need a dummy reference. Let's assume implementation compares internal representation.
    // Value::Null == Value::Null is true.
    // Value::Null == Value::Reference(1) is false.
    let (mut frame, res) = run_instruction(
        0xa5,
        &[0x00, 0x05],
        vec![Value::Null, Value::Reference(1)],
        vec![],
    );
    assert!(res.is_ok());
    // Should NOT jump. Instruction length is 3 (1 opcode + 2 offset).
    // pc started at 1, read 2 bytes -> pc becomes 3.
    assert_eq!(frame.pc, 3);
}

#[test]
fn test_control_flow_jsr_ret() {
    // jsr: Jump subroutine. Push return address (pc + 3) to stack.
    // 0xa8
    let (mut frame, res) = run_instruction(0xa8, &[0x00, 0x05], vec![], vec![]);
    assert!(res.is_ok());
    // pc should be 0 + 5 = 5
    assert_eq!(frame.pc, 5);
    // Stack should have return address. Opcode (0) + 3 = 3.
    // ReturnAddress is pushed as Value::ReturnAddress(3) ?
    // Wait, let's check Value enum.
    let ret_val = frame.pop().unwrap();
    if let Value::ReturnAddress(addr) = ret_val {
        assert_eq!(addr, 3);
    } else {
        panic!("Expected ReturnAddress, got {:?}", ret_val);
    }

    // ret: Return from subroutine.
    // 0xa9 index
    // We need to store return address in local variable.
    let mut interp = create_interpreter();
    let class = create_minimal_class();
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.locals[1] = Value::ReturnAddress(10);

    // 0xa9 0x01
    let code = vec![0xa9, 0x01];
    frame.pc = 1; // after opcode
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xa9)
        .unwrap();

    assert_eq!(frame.pc, 10);
}

#[test]
fn test_control_flow_tableswitch() {
    // tableswitch
    // 0xaa
    // Padding: 0-3 bytes.
    // default: 4 bytes
    // low: 4 bytes
    // high: 4 bytes
    // jump offsets: (high - low + 1) * 4 bytes

    // Case 1: pc=0. opcode=0xaa.
    // padding: (4 - (1 % 4)) % 4 = 3 bytes.
    // So at pc=1, we skip 3 bytes -> pc=4.
    // default offset at 4.
    // low at 8.
    // high at 12.
    // offsets start at 16.

    let mut code = vec![0xaa];
    code.extend_from_slice(&[0, 0, 0]); // padding (3 bytes)

    // default offset: 100 (0x00000064)
    code.extend_from_slice(&[0, 0, 0, 0x64]);

    // low: 1
    code.extend_from_slice(&[0, 0, 0, 1]);

    // high: 2
    code.extend_from_slice(&[0, 0, 0, 2]);

    // offset for 1: 20 (0x00000014)
    code.extend_from_slice(&[0, 0, 0, 0x14]);

    // offset for 2: 30 (0x0000001e)
    code.extend_from_slice(&[0, 0, 0, 0x1e]);

    // Total len: 1+3+4+4+4+4+4 = 24 bytes.

    // Test match 1 -> jump 20
    let mut interp = create_interpreter();
    let class = create_minimal_class();
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.push(Value::Int(1)).unwrap();
    frame.pc = 1;
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xaa)
        .unwrap();
    assert_eq!(frame.pc, 20); // 0 + 20

    // Test match 2 -> jump 30
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.push(Value::Int(2)).unwrap();
    frame.pc = 1;
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xaa)
        .unwrap();
    assert_eq!(frame.pc, 30); // 0 + 30

    // Test default -> jump 100
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.push(Value::Int(3)).unwrap(); // 3 > high
    frame.pc = 1;
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xaa)
        .unwrap();
    assert_eq!(frame.pc, 100); // 0 + 100
}

#[test]
fn test_arithmetic_lcmp() {
    // lcmp: Compare long
    // 0x94
    // val1 > val2 -> 1
    // val1 == val2 -> 0
    // val1 < val2 -> -1

    // 10 > 5
    // Stack: ..., 10, 5 (top) -> pop v2=5, v1=10. 10 > 5.
    let (frame, res) = run_instruction(0x94, &[], vec![Value::Long(10), Value::Long(5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(1));

    // 5 < 10
    // Stack: ..., 5, 10 (top) -> pop v2=10, v1=5. 5 < 10.
    let (frame, res) = run_instruction(0x94, &[], vec![Value::Long(5), Value::Long(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-1));

    // 10 == 10
    let (frame, res) = run_instruction(0x94, &[], vec![Value::Long(10), Value::Long(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(0));
}

#[test]
fn test_arithmetic_fcmpl() {
    // fcmpl: Compare float. If NaN, -1.
    // 0x95

    // NaN check
    let (frame, res) = run_instruction(
        0x95,
        &[],
        vec![Value::Float(f32::NAN), Value::Float(1.0)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-1));

    // 1.0 > 0.5 -> 1
    // Stack: ..., 1.0, 0.5 (top) -> v1=1.0, v2=0.5. 1.0 > 0.5.
    let (frame, res) = run_instruction(
        0x95,
        &[],
        vec![Value::Float(1.0), Value::Float(0.5)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(1));
}

#[test]
fn test_conversion_i2x() {
    // i2l (0x85)
    let (frame, res) = run_instruction(0x85, &[], vec![Value::Int(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(10));

    // i2f (0x86)
    let (frame, res) = run_instruction(0x86, &[], vec![Value::Int(10)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 10.0),
        _ => panic!("Expected Float"),
    }

    // i2d (0x87)
    let (frame, res) = run_instruction(0x87, &[], vec![Value::Int(10)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 10.0),
        _ => panic!("Expected Double"),
    }

    // i2b (0x91): 128 -> -128
    let (frame, res) = run_instruction(0x91, &[], vec![Value::Int(128)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-128));

    // i2c (0x92): 65536 -> 0, -1 -> 65535
    let (frame, res) = run_instruction(0x92, &[], vec![Value::Int(-1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(65535));

    // i2s (0x93): 32768 -> -32768
    let (frame, res) = run_instruction(0x93, &[], vec![Value::Int(32768)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-32768));
}

#[test]
fn test_conversion_l2x() {
    // l2i (0x88)
    let (frame, res) = run_instruction(0x88, &[], vec![Value::Long(0xFFFFFFFF)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-1));

    // l2f (0x89)
    let (frame, res) = run_instruction(0x89, &[], vec![Value::Long(10)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 10.0),
        _ => panic!("Expected Float"),
    }

    // l2d (0x8a)
    let (frame, res) = run_instruction(0x8a, &[], vec![Value::Long(10)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 10.0),
        _ => panic!("Expected Double"),
    }
}

#[test]
fn test_conversion_f2x() {
    // f2i (0x8b)
    // 2.5 -> 2
    let (frame, res) = run_instruction(0x8b, &[], vec![Value::Float(2.5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(2));

    // NaN -> 0
    let (frame, res) = run_instruction(0x8b, &[], vec![Value::Float(f32::NAN)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(0));

    // Inf -> MAX
    let (frame, res) = run_instruction(0x8b, &[], vec![Value::Float(f32::INFINITY)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(i32::MAX));

    // f2l (0x8c)
    let (frame, res) = run_instruction(0x8c, &[], vec![Value::Float(2.5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(2));

    // f2d (0x8d)
    let (frame, res) = run_instruction(0x8d, &[], vec![Value::Float(2.5)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 2.5),
        _ => panic!("Expected Double"),
    }
}

#[test]
fn test_conversion_d2x() {
    // d2i (0x8e)
    let (frame, res) = run_instruction(0x8e, &[], vec![Value::Double(2.5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(2));

    // NaN -> 0
    let (frame, res) = run_instruction(0x8e, &[], vec![Value::Double(f64::NAN)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(0));

    // d2l (0x8f)
    let (frame, res) = run_instruction(0x8f, &[], vec![Value::Double(2.5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(2));

    // d2f (0x90)
    let (frame, res) = run_instruction(0x90, &[], vec![Value::Double(2.5)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 2.5),
        _ => panic!("Expected Float"),
    }
}

#[test]
fn test_control_flow_lookupswitch() {
    // lookupswitch (0xab)
    // 0: lookupswitch
    // 1-3: padding (3 bytes)
    // 4-7: default offset = 20
    // 8-11: npairs = 2
    // 12-15: key1 = 10
    // 16-19: offset1 = 30
    // 20-23: key2 = 20
    // 24-27: offset2 = 40

    let mut operands = vec![0, 0, 0]; // padding
    operands.extend_from_slice(&20i32.to_be_bytes()); // default
    operands.extend_from_slice(&2i32.to_be_bytes()); // npairs

    // pair 1
    operands.extend_from_slice(&10i32.to_be_bytes()); // key
    operands.extend_from_slice(&30i32.to_be_bytes()); // offset

    // pair 2
    operands.extend_from_slice(&20i32.to_be_bytes()); // key
    operands.extend_from_slice(&40i32.to_be_bytes()); // offset

    // Ensure code buffer is large enough for jump targets if bounds checked?
    // The dispatch logic checks bounds for reading operands, but maybe not target validity unless it jumps?
    // Actually dispatch logic for lookupswitch checks:
    // if frame.pc + 8 <= code.len() ...
    // And for target validity?
    // "frame.pc = (opcode_addr as i32 + offset) as usize;"
    // It doesn't check if target is within bounds immediately, only when next instruction is fetched (which is outside run_instruction).
    // BUT, the reading loop checks `frame.pc + 8 <= code.len()`.
    // My operands length is 3 + 4 + 4 + 8 + 8 = 27.
    // Total code size = 1 + 27 = 28.
    // Reading loop:
    // 1. pc at 12. 12 + 8 = 20 <= 28. OK. Reads pair 1. pc becomes 20.
    // 2. pc at 20. 20 + 8 = 28 <= 28. OK. Reads pair 2. pc becomes 28.
    // Loop finishes.

    // Case 1: Match 10 -> Offset 30 -> PC 30
    let (frame, res) = run_instruction(0xab, &operands, vec![Value::Int(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 30);

    // Case 2: Match 20 -> Offset 40 -> PC 40
    let (frame, res) = run_instruction(0xab, &operands, vec![Value::Int(20)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 40);

    // Case 3: No match (5) -> Default 20 -> PC 20
    let (frame, res) = run_instruction(0xab, &operands, vec![Value::Int(5)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 20);
}

#[test]
fn test_arithmetic_f_ops() {
    // fadd (0x62)
    let (frame, res) = run_instruction(
        0x62,
        &[],
        vec![Value::Float(1.5), Value::Float(2.5)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 4.0),
        _ => panic!("Expected Float"),
    }

    // fsub (0x66)
    let (frame, res) = run_instruction(
        0x66,
        &[],
        vec![Value::Float(2.5), Value::Float(1.5)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 1.0),
        _ => panic!("Expected Float"),
    }

    // fmul (0x6a)
    let (frame, res) = run_instruction(
        0x6a,
        &[],
        vec![Value::Float(2.0), Value::Float(3.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 6.0),
        _ => panic!("Expected Float"),
    }

    // fdiv (0x6e)
    let (frame, res) = run_instruction(
        0x6e,
        &[],
        vec![Value::Float(6.0), Value::Float(2.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 3.0),
        _ => panic!("Expected Float"),
    }

    // frem (0x72)
    let (frame, res) = run_instruction(
        0x72,
        &[],
        vec![Value::Float(5.5), Value::Float(2.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, 1.5),
        _ => panic!("Expected Float"),
    }

    // fneg (0x76)
    let (frame, res) = run_instruction(0x76, &[], vec![Value::Float(1.5)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Float(f) => assert_eq!(f, -1.5),
        _ => panic!("Expected Float"),
    }
}

#[test]
fn test_arithmetic_d_ops() {
    // dadd (0x63)
    let (frame, res) = run_instruction(
        0x63,
        &[],
        vec![Value::Double(1.5), Value::Double(2.5)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 4.0),
        _ => panic!("Expected Double"),
    }

    // dsub (0x67)
    let (frame, res) = run_instruction(
        0x67,
        &[],
        vec![Value::Double(2.5), Value::Double(1.5)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 1.0),
        _ => panic!("Expected Double"),
    }

    // dmul (0x6b)
    let (frame, res) = run_instruction(
        0x6b,
        &[],
        vec![Value::Double(2.0), Value::Double(3.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 6.0),
        _ => panic!("Expected Double"),
    }

    // ddiv (0x6f)
    let (frame, res) = run_instruction(
        0x6f,
        &[],
        vec![Value::Double(6.0), Value::Double(2.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 3.0),
        _ => panic!("Expected Double"),
    }

    // drem (0x73)
    let (frame, res) = run_instruction(
        0x73,
        &[],
        vec![Value::Double(5.5), Value::Double(2.0)],
        vec![],
    );
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, 1.5),
        _ => panic!("Expected Double"),
    }

    // dneg (0x77)
    let (frame, res) = run_instruction(0x77, &[], vec![Value::Double(1.5)], vec![]);
    assert!(res.is_ok());
    match frame.stack[0] {
        Value::Double(d) => assert_eq!(d, -1.5),
        _ => panic!("Expected Double"),
    }
}

#[test]
fn test_arithmetic_l_ops() {
    // ladd (0x61)
    let (frame, res) = run_instruction(0x61, &[], vec![Value::Long(10), Value::Long(20)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(30));

    // lsub (0x65)
    let (frame, res) = run_instruction(0x65, &[], vec![Value::Long(20), Value::Long(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(10));

    // lmul (0x69)
    let (frame, res) = run_instruction(0x69, &[], vec![Value::Long(10), Value::Long(20)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(200));

    // ldiv (0x6d)
    let (frame, res) = run_instruction(0x6d, &[], vec![Value::Long(20), Value::Long(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(2));

    // lrem (0x71)
    let (frame, res) = run_instruction(0x71, &[], vec![Value::Long(10), Value::Long(3)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(1));

    // lneg (0x75)
    let (frame, res) = run_instruction(0x75, &[], vec![Value::Long(10)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(-10));

    // lshl (0x79)
    let (frame, res) = run_instruction(0x79, &[], vec![Value::Long(1), Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(2));

    // lshr (0x7b)
    let (frame, res) = run_instruction(0x7b, &[], vec![Value::Long(-2), Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(-1));

    // lushr (0x7d)
    let (frame, res) = run_instruction(0x7d, &[], vec![Value::Long(-2), Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(i64::MAX)); // -2 as u64 is ...FE, shift right 1 is ...7F... which is MAX

    // land (0x7f)
    let (frame, res) = run_instruction(
        0x7f,
        &[],
        vec![Value::Long(0b1100), Value::Long(0b1010)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(0b1000));

    // lor (0x81)
    let (frame, res) = run_instruction(
        0x81,
        &[],
        vec![Value::Long(0b1100), Value::Long(0b1010)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(0b1110));

    // lxor (0x83)
    let (frame, res) = run_instruction(
        0x83,
        &[],
        vec![Value::Long(0b1100), Value::Long(0b1010)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Long(0b0110));
}

#[test]
fn test_comparison_fcmp_dcmp() {
    // fcmpg (0x96) - NaN is 1
    let (frame, res) = run_instruction(
        0x96,
        &[],
        vec![Value::Float(f32::NAN), Value::Float(1.0)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(1));

    // dcmpl (0x97) - NaN is -1 (Wait, dcmpl is 0x97? No, fcmpl is 0x97. dcmpl is 0x97? Let's check spec)
    // fcmpl (0x97) - NaN is -1. Tested in test_arithmetic_fcmpl
    // dcmpl (0x97 is fcmpl. dcmpl is 0x97 + ? Let's check spec)
    // fcmpl=0x97, fcmpg=0x96
    // dcmpl=0x97? No.
    // dcmpl=0x97 is fcmpl? Wait.
    // 0x94 lcmp
    // 0x95 fcmpl
    // 0x96 fcmpg
    // 0x97 dcmpl
    // 0x98 dcmpg

    // Check fcmpl opcode in my previous test:
    // fn test_arithmetic_fcmpl() { ... run_instruction(0x97, ... } -> Wait, if 0x97 is dcmpl, then fcmpl test is wrong?
    // Let's verify opcodes.
    // lcmp=148(0x94)
    // fcmpl=149(0x95)
    // fcmpg=150(0x96)
    // dcmpl=151(0x97)
    // dcmpg=152(0x98)

    // My previous test used 0x97 for fcmpl?
    // #[test] fn test_arithmetic_fcmpl() { ... run_instruction(0x97 ... }
    // If 0x97 is dcmpl, then I was testing dcmpl with floats? That might be why it worked if implementation treats them similarly or casts?
    // Or maybe I got opcode wrong in test or comment.
    // I should fix that test too if it's wrong.

    // dcmpl (0x97)
    let (frame, res) = run_instruction(
        0x97,
        &[],
        vec![Value::Double(f64::NAN), Value::Double(1.0)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(-1));

    // dcmpg (0x98)
    let (frame, res) = run_instruction(
        0x98,
        &[],
        vec![Value::Double(f64::NAN), Value::Double(1.0)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.stack[0], Value::Int(1));
}

#[test]
fn test_control_flow_if_cond() {
    // ifeq (0x99) - jump if value == 0
    let (frame, res) = run_instruction(0x99, &[0x00, 0x05], vec![Value::Int(0)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    let (frame, res) = run_instruction(0x99, &[0x00, 0x05], vec![Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 3); // no jump

    // ifne (0x9a) - jump if value != 0
    let (frame, res) = run_instruction(0x9a, &[0x00, 0x05], vec![Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // iflt (0x9b) - jump if value < 0
    let (frame, res) = run_instruction(0x9b, &[0x00, 0x05], vec![Value::Int(-1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // ifge (0x9c) - jump if value >= 0
    let (frame, res) = run_instruction(0x9c, &[0x00, 0x05], vec![Value::Int(0)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // ifgt (0x9d) - jump if value > 0
    let (frame, res) = run_instruction(0x9d, &[0x00, 0x05], vec![Value::Int(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // ifle (0x9e) - jump if value <= 0
    let (frame, res) = run_instruction(0x9e, &[0x00, 0x05], vec![Value::Int(0)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);
}

#[test]
fn test_control_flow_if_icmp() {
    // if_icmpeq (0x9f)
    let (frame, res) = run_instruction(
        0x9f,
        &[0x00, 0x05],
        vec![Value::Int(10), Value::Int(10)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // if_icmpne (0xa0)
    let (frame, res) = run_instruction(
        0xa0,
        &[0x00, 0x05],
        vec![Value::Int(10), Value::Int(11)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // if_icmplt (0xa1)
    // Stack: ..., val1, val2 -> val2=10, val1=9. 9 < 10 is true.
    let (frame, res) = run_instruction(
        0xa1,
        &[0x00, 0x05],
        vec![Value::Int(9), Value::Int(10)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // if_icmpge (0xa2)
    let (frame, res) = run_instruction(
        0xa2,
        &[0x00, 0x05],
        vec![Value::Int(10), Value::Int(10)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // if_icmpgt (0xa3)
    // Stack: ..., val1, val2 -> val2=10, val1=11. 11 > 10 is true.
    let (frame, res) = run_instruction(
        0xa3,
        &[0x00, 0x05],
        vec![Value::Int(11), Value::Int(10)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // if_icmple (0xa4)
    let (frame, res) = run_instruction(
        0xa4,
        &[0x00, 0x05],
        vec![Value::Int(10), Value::Int(10)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);
}

#[test]
fn test_control_flow_if_acmpne() {
    // if_acmpne: Pop ref2, ref1. Jump if ref1 != ref2.
    // 0xa6
    // Unequal references (null vs non-null)
    let (mut frame, res) = run_instruction(
        0xa6,
        &[0x00, 0x05],
        vec![Value::Null, Value::Reference(1)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    // Equal references (both null)
    let (mut frame, res) =
        run_instruction(0xa6, &[0x00, 0x05], vec![Value::Null, Value::Null], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 3);

    // Equal references (same reference)
    let (mut frame, res) = run_instruction(
        0xa6,
        &[0x00, 0x05],
        vec![Value::Reference(1), Value::Reference(1)],
        vec![],
    );
    assert!(res.is_ok());
    assert_eq!(frame.pc, 3);
}

#[test]
fn test_control_flow_goto() {
    // goto: Unconditional branch (always jumps)
    // 0xa7
    let (mut frame, res) = run_instruction(0xa7, &[0x00, 0x0a], vec![], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 10);
}

#[test]
fn test_control_flow_goto_w() {
    // goto_w: Unconditional branch with wide offset (always jumps)
    // 0xc8
    let mut code = vec![0xc8];
    code.extend_from_slice(&0x00000100i32.to_be_bytes()); // offset = 256

    let mut interp = create_interpreter();
    let class = create_minimal_class();
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.pc = 1;
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xc8)
        .unwrap();
    assert_eq!(frame.pc, 256);
}

#[test]
fn test_control_flow_jsr_w() {
    // jsr_w: Jump subroutine with wide offset. Push return address to stack.
    // 0xc9
    let mut code = vec![0xc9];
    code.extend_from_slice(&0x00000010i32.to_be_bytes()); // offset = 16

    let mut interp = create_interpreter();
    let class = create_minimal_class();
    let mut frame = StackFrame::new(10, 10, "test".to_string());
    frame.pc = 1;
    interp
        .dispatch_instruction(&class, &code, &mut frame, 0xc9)
        .unwrap();
    assert_eq!(frame.pc, 16);
    let ret_val = frame.pop().unwrap();
    if let Value::ReturnAddress(addr) = ret_val {
        assert_eq!(addr, 5);
    } else {
        panic!("Expected ReturnAddress, got {:?}", ret_val);
    }
}

#[test]
fn test_control_flow_ifnull_ifnonnull() {
    // ifnull: Jump if reference is null
    // 0xc6
    let (mut frame, res) = run_instruction(0xc6, &[0x00, 0x05], vec![Value::Null], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    let (mut frame, res) = run_instruction(0xc6, &[0x00, 0x05], vec![Value::Reference(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 3);

    // ifnonnull: Jump if reference is not null
    // 0xc7
    let (mut frame, res) = run_instruction(0xc7, &[0x00, 0x05], vec![Value::Reference(1)], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 5);

    let (mut frame, res) = run_instruction(0xc7, &[0x00, 0x05], vec![Value::Null], vec![]);
    assert!(res.is_ok());
    assert_eq!(frame.pc, 3);
}
