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

#[test]
fn test_invokedynamic_string_concat() {
    let mut interp = Interpreter::new();
    let mut class = ClassFile {
        magic: 0xCAFEBABE,
        minor_version: 0,
        major_version: 52,
        constant_pool: vec![
            ConstantPoolEntry::ConstantUtf8 { bytes: vec![] }, // 0 (dummy)
            
            // 1: Utf8 "java/lang/invoke/StringConcatFactory"
            ConstantPoolEntry::ConstantUtf8 { bytes: "java/lang/invoke/StringConcatFactory".as_bytes().to_vec() },
            // 2: Class #1
            ConstantPoolEntry::ConstantClass { name_index: 1 },
            
            // 3: Utf8 "makeConcatWithConstants"
            ConstantPoolEntry::ConstantUtf8 { bytes: "makeConcatWithConstants".as_bytes().to_vec() },
            // 4: Utf8 descriptor
            ConstantPoolEntry::ConstantUtf8 { bytes: "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;".as_bytes().to_vec() },
            
            // 5: NameAndType #3 : #4
            ConstantPoolEntry::ConstantNameAndType { name_index: 3, descriptor_index: 4 },
            
            // 6: Methodref #2 . #5 (Bootstrap Method Ref)
            ConstantPoolEntry::ConstantMethodref { class_index: 2, name_and_type_index: 5 },
            
            // 7: MethodHandle 6 (REF_invokeStatic) #6
            ConstantPoolEntry::ConstantMethodHandle { reference_kind: 6, reference_index: 6 },
            
            // 8: Utf8 "makeConcat" (invokedynamic name)
            ConstantPoolEntry::ConstantUtf8 { bytes: "makeConcat".as_bytes().to_vec() },
            // 9: Utf8 "(Ljava/lang/String;I)Ljava/lang/String;" (descriptor: String, int -> String)
            ConstantPoolEntry::ConstantUtf8 { bytes: "(Ljava/lang/String;I)Ljava/lang/String;".as_bytes().to_vec() },
            
            // 10: NameAndType #8 : #9
            ConstantPoolEntry::ConstantNameAndType { name_index: 8, descriptor_index: 9 },
            
            // 11: InvokeDynamic 0 : #10 (bootstrap_method_attr_index = 0)
            ConstantPoolEntry::ConstantInvokeDynamic { bootstrap_method_attr_index: 0, name_and_type_index: 10 },
            
            // 12: Utf8 "BootstrapMethods"
            ConstantPoolEntry::ConstantUtf8 { bytes: "BootstrapMethods".as_bytes().to_vec() },
            
            // 13: Utf8 "\u{1} prefix \u{1} suffix" (Recipe)
            // \u{1} is 0x01.
            ConstantPoolEntry::ConstantUtf8 { bytes: vec![1, b' ', b'p', b'r', b'e', b'f', b'i', b'x', b' ', 1, b' ', b's', b'u', b'f', b'f', b'i', b'x'] },
            // 14: String #13
            ConstantPoolEntry::ConstantString { string_index: 13 },
        ],
        access_flags: 0x0021,
        this_class: 2,
        super_class: 0,
        interfaces: vec![],
        fields: vec![],
        methods: vec![],
        attributes: vec![],
    };

    // Construct BootstrapMethods attribute
    // u2 num_bootstrap_methods = 1
    //   u2 bootstrap_method_ref = 7
    //   u2 num_bootstrap_arguments = 1
    //   u2 bootstrap_arguments[0] = 14
    let mut info = Vec::new();
    info.extend_from_slice(&1u16.to_be_bytes()); // num_methods
    info.extend_from_slice(&7u16.to_be_bytes()); // method_ref
    info.extend_from_slice(&1u16.to_be_bytes()); // num_args
    info.extend_from_slice(&14u16.to_be_bytes()); // arg0 (recipe)

    class.attributes.push(AttributeInfo {
        attribute_name_index: 12,
        info,
    });

    let mut frame = StackFrame::new(10, 10, "test".to_string());

    // Push arguments: String("Hello"), Int(42)
    let hello_addr = interp.memory.heap.allocate_string("Hello".to_string());
    frame.stack.push(Value::Reference(hello_addr));
    frame.stack.push(Value::Int(42));

    // Call handle_invokedynamic with index 11
    let res = interp.handle_invokedynamic(&class, &mut frame, 11);
    assert!(res.is_ok());

    // Result should be String reference
    let result_val = frame.stack.pop().unwrap();
    if let Value::Reference(addr) = result_val {
        let obj = interp.memory.heap.get_object(addr).unwrap();
        let s = obj.string_data.as_ref().unwrap();
        // Expected: "Hello prefix 42 suffix"
        assert_eq!(s, "Hello prefix 42 suffix");
    } else {
        panic!("Expected Reference result, got {:?}", result_val);
    }
}
