use rasm_core::{
    codegen::{emit::Emitter, encode::encode_instruction, reloc::RelocTable},
    disasm,
    isa::{GPRs, Instruction, MemoryAddress, MnemonicKind, Operand, Registers, Scale},
};
use std::collections::HashMap;

fn roundtrip(instr: &Instruction, expected_bytes: Option<&[u8]>) {
    let mut emit = Emitter::new();
    let mut relocs = RelocTable::new();
    let label_offsets = HashMap::new();
    encode_instruction(instr, &mut emit, &mut relocs, &label_offsets)
        .unwrap_or_else(|e| panic!("encode failed for {:?}: {}", instr, e));

    let bytes = &emit.buf;

    if let Some(expected) = expected_bytes {
        assert_eq!(bytes.as_slice(), expected, "byte mismatch for {:?}", instr);
    }

    let decoded = disasm::disassemble_all(bytes)
        .unwrap_or_else(|e| panic!("decode failed for bytes {bytes:02X?} ({:?}): {}", instr, e));

    assert_eq!(
        decoded.len(),
        1,
        "expected 1 instruction, got {} for {:?}",
        decoded.len(),
        instr
    );
    assert_eq!(
        decoded[0].bytes_len,
        bytes.len(),
        "length mismatch for {:?}: decoded {} vs encoded {}",
        instr,
        decoded[0].bytes_len,
        bytes.len()
    );
    assert_eq!(
        decoded[0].instruction, *instr,
        "instruction mismatch for {:?}: decoded {:?}",
        instr, decoded[0].instruction
    );
}

fn reg(gpr: GPRs) -> Operand {
    Operand::Register(Registers::GPR(gpr))
}

fn imm(val: i64) -> Operand {
    Operand::Immediate(val)
}

fn mem_exp(
    base: Option<GPRs>,
    index: Option<GPRs>,
    scale: Option<u8>,
    displacement: i32,
) -> Operand {
    Operand::Memory(MemoryAddress {
        base: base.map(Registers::GPR),
        index,
        scale: scale.map(|s| match s {
            1 => Scale::One,
            2 => Scale::Two,
            4 => Scale::Four,
            8 => Scale::Eight,
            _ => panic!("bad scale"),
        }),
        displacement,
        label: None,
    })
}

fn mk_instr(m: MnemonicKind, op: Operand) -> Instruction {
    Instruction {
        mnemonic: m,
        operands: vec![op],
    }
}

fn mk_instr2(m: MnemonicKind, op1: Operand, op2: Operand) -> Instruction {
    Instruction {
        mnemonic: m,
        operands: vec![op1, op2],
    }
}

fn mk_instr3(m: MnemonicKind, op1: Operand, op2: Operand, op3: Operand) -> Instruction {
    Instruction {
        mnemonic: m,
        operands: vec![op1, op2, op3],
    }
}

fn mk_instr0(m: MnemonicKind) -> Instruction {
    Instruction {
        mnemonic: m,
        operands: vec![],
    }
}

// =========================================================================
// Zero-operand instructions
// =========================================================================

#[test]
fn roundtrip_ret() {
    roundtrip(&mk_instr0(MnemonicKind::Ret), Some(&[0xC3]));
}
#[test]
fn roundtrip_nop() {
    roundtrip(&mk_instr0(MnemonicKind::Nop), Some(&[0x90]));
}
#[test]
fn roundtrip_syscall() {
    roundtrip(&mk_instr0(MnemonicKind::Syscall), Some(&[0x0F, 0x05]));
}
#[test]
fn roundtrip_cqo() {
    roundtrip(&mk_instr0(MnemonicKind::Cqo), Some(&[0x48, 0x99]));
}

// =========================================================================
// PUSH r64
// =========================================================================

#[test]
fn roundtrip_push_rax() {
    roundtrip(&mk_instr(MnemonicKind::Push, reg(GPRs::Rax)), Some(&[0x50]));
}
#[test]
fn roundtrip_push_rbp() {
    roundtrip(&mk_instr(MnemonicKind::Push, reg(GPRs::Rbp)), Some(&[0x55]));
}
#[test]
fn roundtrip_push_r8() {
    roundtrip(
        &mk_instr(MnemonicKind::Push, reg(GPRs::R8)),
        Some(&[0x41, 0x50]),
    );
}
#[test]
fn roundtrip_push_r15() {
    roundtrip(
        &mk_instr(MnemonicKind::Push, reg(GPRs::R15)),
        Some(&[0x41, 0x57]),
    );
}
#[test]
fn roundtrip_push_r12() {
    roundtrip(
        &mk_instr(MnemonicKind::Push, reg(GPRs::R12)),
        Some(&[0x41, 0x54]),
    );
}
#[test]
fn roundtrip_push_imm() {
    roundtrip(
        &mk_instr(MnemonicKind::Push, imm(0x2A)),
        Some(&[0x68, 0x2A, 0x00, 0x00, 0x00]),
    );
}

// =========================================================================
// POP r64
// =========================================================================

#[test]
fn roundtrip_pop_rax() {
    roundtrip(&mk_instr(MnemonicKind::Pop, reg(GPRs::Rax)), Some(&[0x58]));
}
#[test]
fn roundtrip_pop_r8() {
    roundtrip(
        &mk_instr(MnemonicKind::Pop, reg(GPRs::R8)),
        Some(&[0x41, 0x58]),
    );
}

// =========================================================================
// MOV r64, r64
// =========================================================================

#[test]
fn roundtrip_mov_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x89, 0xD8]),
    );
}
#[test]
fn roundtrip_mov_r8_r9() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::R8), reg(GPRs::R9)),
        Some(&[0x4D, 0x89, 0xC8]),
    );
}
#[test]
fn roundtrip_mov_r9_r8() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::R9), reg(GPRs::R8)),
        Some(&[0x4D, 0x89, 0xC1]),
    );
}
#[test]
fn roundtrip_mov_r10_r15() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::R10), reg(GPRs::R15)),
        Some(&[0x4D, 0x89, 0xFA]),
    );
}

// =========================================================================
// MOV r64, imm64
// =========================================================================

#[test]
fn roundtrip_mov_rax_0() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0)),
        Some(&[0x48, 0xB8, 0, 0, 0, 0, 0, 0, 0, 0]),
    );
}
#[test]
fn roundtrip_mov_rax_imm() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0x2A)),
        Some(&[0x48, 0xB8, 0x2A, 0, 0, 0, 0, 0, 0, 0]),
    );
}
#[test]
fn roundtrip_mov_r8_imm() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::R8), imm(0xFF)),
        Some(&[0x49, 0xB8, 0xFF, 0, 0, 0, 0, 0, 0, 0]),
    );
}
#[test]
fn roundtrip_mov_r15_large() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::R15), imm(0x12345678)),
        Some(&[0x49, 0xBF, 0x78, 0x56, 0x34, 0x12, 0, 0, 0, 0]),
    );
}
#[test]
fn roundtrip_mov_rax_neg1() {
    roundtrip(
        &mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(-1)),
        Some(&[0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
    );
}

// =========================================================================
// MOV [mem], r64  and  MOV r64, [mem]
// =========================================================================

#[test]
fn roundtrip_mov_mem_rbx_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbx), None, None, 0),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x03]),
    );
}

#[test]
fn roundtrip_mov_rax_mem_rbx() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            reg(GPRs::Rax),
            mem_exp(Some(GPRs::Rbx), None, None, 0),
        ),
        Some(&[0x48, 0x8B, 0x03]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_plus_8_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), None, None, 8),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x45, 0x08]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_minus_8_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), None, None, -8),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x45, 0xF8]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_minus_128_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), None, None, -128),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x45, 0x80]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_plus_127_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), None, None, 127),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x45, 0x7F]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_plus_128_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), None, None, 128),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x85, 0x80, 0x00, 0x00, 0x00]),
    );
}

#[test]
fn roundtrip_mov_mem_rax_plus_256_r8() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rax), None, None, 256),
            reg(GPRs::R8),
        ),
        Some(&[0x4C, 0x89, 0x80, 0x00, 0x01, 0x00, 0x00]),
    );
}

// =========================================================================
// MOV [mem], imm32
// =========================================================================

#[test]
fn roundtrip_mov_mem_rax_imm() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rax), None, None, 0),
            imm(0x2A),
        ),
        Some(&[0x48, 0xC7, 0x00, 0x2A, 0x00, 0x00, 0x00]),
    );
}

// =========================================================================
// LEA r64, [mem]
// =========================================================================

#[test]
fn roundtrip_lea_rax_rbx() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Lea,
            reg(GPRs::Rax),
            mem_exp(Some(GPRs::Rbx), None, None, 0),
        ),
        Some(&[0x48, 0x8D, 0x03]),
    );
}

#[test]
fn roundtrip_lea_r8_rbp_minus_16() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Lea,
            reg(GPRs::R8),
            mem_exp(Some(GPRs::Rbp), None, None, -16),
        ),
        Some(&[0x4C, 0x8D, 0x45, 0xF0]),
    );
}

// =========================================================================
// ADD / SUB / XOR / AND / OR / CMP r64, r64
// =========================================================================

#[test]
fn roundtrip_add_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Add, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x01, 0xD8]),
    );
}
#[test]
fn roundtrip_sub_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Sub, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x29, 0xD8]),
    );
}
#[test]
fn roundtrip_xor_rax_rax() {
    roundtrip(
        &mk_instr2(MnemonicKind::Xor, reg(GPRs::Rax), reg(GPRs::Rax)),
        Some(&[0x48, 0x31, 0xC0]),
    );
}
#[test]
fn roundtrip_and_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::And, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x21, 0xD8]),
    );
}
#[test]
fn roundtrip_or_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Or, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x09, 0xD8]),
    );
}
#[test]
fn roundtrip_cmp_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Cmp, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x39, 0xD8]),
    );
}

#[test]
fn roundtrip_add_r8_r9() {
    roundtrip(
        &mk_instr2(MnemonicKind::Add, reg(GPRs::R8), reg(GPRs::R9)),
        Some(&[0x4D, 0x01, 0xC8]),
    );
}
#[test]
fn roundtrip_sub_r10_r15() {
    roundtrip(
        &mk_instr2(MnemonicKind::Sub, reg(GPRs::R10), reg(GPRs::R15)),
        Some(&[0x4D, 0x29, 0xFA]),
    );
}

// =========================================================================
// ALU r64, imm32 / imm8
// =========================================================================

#[test]
fn roundtrip_add_rax_imm8_pos() {
    roundtrip(
        &mk_instr2(MnemonicKind::Add, reg(GPRs::Rax), imm(0x7F)),
        Some(&[0x48, 0x83, 0xC0, 0x7F]),
    );
}
#[test]
fn roundtrip_sub_rax_imm8_neg() {
    roundtrip(
        &mk_instr2(MnemonicKind::Sub, reg(GPRs::Rax), imm(-128)),
        Some(&[0x48, 0x83, 0xE8, 0x80]),
    );
}
#[test]
fn roundtrip_add_rax_imm32() {
    roundtrip(
        &mk_instr2(MnemonicKind::Add, reg(GPRs::Rax), imm(0x100)),
        Some(&[0x48, 0x81, 0xC0, 0x00, 0x01, 0x00, 0x00]),
    );
}
#[test]
fn roundtrip_xor_rax_imm32() {
    roundtrip(
        &mk_instr2(MnemonicKind::Xor, reg(GPRs::Rax), imm(0x12345678)),
        Some(&[0x48, 0x81, 0xF0, 0x78, 0x56, 0x34, 0x12]),
    );
}
#[test]
fn roundtrip_sub_r8_imm32() {
    roundtrip(
        &mk_instr2(MnemonicKind::Sub, reg(GPRs::R8), imm(0x100)),
        Some(&[0x49, 0x81, 0xE8, 0x00, 0x01, 0x00, 0x00]),
    );
}

// =========================================================================
// ALU r64, [mem]  and  ALU [mem], r64
// =========================================================================

#[test]
fn roundtrip_add_rax_mem_rbx() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Add,
            reg(GPRs::Rax),
            mem_exp(Some(GPRs::Rbx), None, None, 0),
        ),
        Some(&[0x48, 0x03, 0x03]),
    );
}

#[test]
fn roundtrip_add_mem_rbx_rax() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Add,
            mem_exp(Some(GPRs::Rbx), None, None, 0),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x01, 0x03]),
    );
}

// =========================================================================
// TEST r64, r64
// =========================================================================

#[test]
fn roundtrip_test_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Test, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x85, 0xD8]),
    );
}
#[test]
fn roundtrip_test_r8_r9() {
    roundtrip(
        &mk_instr2(MnemonicKind::Test, reg(GPRs::R8), reg(GPRs::R9)),
        Some(&[0x4D, 0x85, 0xC8]),
    );
}

// =========================================================================
// TEST r64, imm32
// =========================================================================

#[test]
fn roundtrip_test_rax_imm() {
    roundtrip(
        &mk_instr2(MnemonicKind::Test, reg(GPRs::Rax), imm(0x2A)),
        Some(&[0x48, 0xF7, 0xC0, 0x2A, 0x00, 0x00, 0x00]),
    );
}

// =========================================================================
// INC / DEC / NEG / NOT / MUL / DIV / IDIV / IMUL (unary)
// =========================================================================

#[test]
fn roundtrip_inc_rax() {
    roundtrip(
        &mk_instr(MnemonicKind::Inc, reg(GPRs::Rax)),
        Some(&[0x48, 0xFF, 0xC0]),
    );
}
#[test]
fn roundtrip_dec_r8() {
    roundtrip(
        &mk_instr(MnemonicKind::Dec, reg(GPRs::R8)),
        Some(&[0x49, 0xFF, 0xC8]),
    );
}
#[test]
fn roundtrip_not_rax() {
    roundtrip(
        &mk_instr(MnemonicKind::Not, reg(GPRs::Rax)),
        Some(&[0x48, 0xF7, 0xD0]),
    );
}
#[test]
fn roundtrip_neg_rax() {
    roundtrip(
        &mk_instr(MnemonicKind::Neg, reg(GPRs::Rax)),
        Some(&[0x48, 0xF7, 0xD8]),
    );
}
#[test]
fn roundtrip_mul_rax() {
    roundtrip(
        &mk_instr(MnemonicKind::Mul, reg(GPRs::Rax)),
        Some(&[0x48, 0xF7, 0xE0]),
    );
}
#[test]
fn roundtrip_div_rbx() {
    roundtrip(
        &mk_instr(MnemonicKind::Div, reg(GPRs::Rbx)),
        Some(&[0x48, 0xF7, 0xF3]),
    );
}
#[test]
fn roundtrip_idiv_rbx() {
    roundtrip(
        &mk_instr(MnemonicKind::Idiv, reg(GPRs::Rbx)),
        Some(&[0x48, 0xF7, 0xFB]),
    );
}
#[test]
fn roundtrip_imul_unary() {
    roundtrip(
        &mk_instr(MnemonicKind::Imul, reg(GPRs::Rax)),
        Some(&[0x48, 0xF7, 0xE8]),
    );
}

// =========================================================================
// IMUL r64, r/m64 (0F AF)
// =========================================================================

#[test]
fn roundtrip_imul_rax_rbx() {
    roundtrip(
        &mk_instr2(MnemonicKind::Imul, reg(GPRs::Rax), reg(GPRs::Rbx)),
        Some(&[0x48, 0x0F, 0xAF, 0xC3]),
    );
}
#[test]
fn roundtrip_imul_r8_r9() {
    roundtrip(
        &mk_instr2(MnemonicKind::Imul, reg(GPRs::R8), reg(GPRs::R9)),
        Some(&[0x4D, 0x0F, 0xAF, 0xC1]),
    );
}

// =========================================================================
// IMUL r64, r/m64, imm32 (0x69)
// =========================================================================

#[test]
fn roundtrip_imul_rax_rbx_imm() {
    roundtrip(
        &mk_instr3(
            MnemonicKind::Imul,
            reg(GPRs::Rax),
            reg(GPRs::Rbx),
            imm(0x2A),
        ),
        Some(&[0x48, 0x69, 0xC3, 0x2A, 0x00, 0x00, 0x00]),
    );
}

// =========================================================================
// SHL / SHR / SAR r64, 1/imm8/cl
// =========================================================================

#[test]
fn roundtrip_shl_rax_1() {
    roundtrip(
        &mk_instr2(MnemonicKind::Shl, reg(GPRs::Rax), imm(1)),
        Some(&[0x48, 0xD1, 0xE0]),
    );
}
#[test]
fn roundtrip_shr_rax_1() {
    roundtrip(
        &mk_instr2(MnemonicKind::Shr, reg(GPRs::Rax), imm(1)),
        Some(&[0x48, 0xD1, 0xE8]),
    );
}
#[test]
fn roundtrip_sar_rax_1() {
    roundtrip(
        &mk_instr2(MnemonicKind::Sar, reg(GPRs::Rax), imm(1)),
        Some(&[0x48, 0xD1, 0xF8]),
    );
}
#[test]
fn roundtrip_shl_rax_3() {
    roundtrip(
        &mk_instr2(MnemonicKind::Shl, reg(GPRs::Rax), imm(3)),
        Some(&[0x48, 0xC1, 0xE0, 0x03]),
    );
}
#[test]
fn roundtrip_shl_rax_cl() {
    roundtrip(
        &mk_instr2(
            MnemonicKind::Shl,
            reg(GPRs::Rax),
            Operand::Register(Registers::GPR(GPRs::Rcx)),
        ),
        Some(&[0x48, 0xD3, 0xE0]),
    );
}

// =========================================================================
// SIB addressing modes
// =========================================================================

#[test]
fn roundtrip_mov_mem_rax_rbx4_rax() {
    // [rax + rbx*4]
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rax), Some(GPRs::Rbx), Some(4), 0),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x04, 0x98]),
    );
}

#[test]
fn roundtrip_mov_mem_rsp_rax() {
    // [rsp] — requires SIB even with no index
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rsp), None, None, 0),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x04, 0x24]),
    );
}

#[test]
fn roundtrip_mov_mem_r12_rax() {
    // [r12] — requires SIB since r12 maps to rm=4 (same as rsp)
    // REX.B is needed for r12, so REX byte is 0x49 (W+B)
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::R12), None, None, 0),
            reg(GPRs::Rax),
        ),
        Some(&[0x49, 0x89, 0x04, 0x24]),
    );
}

#[test]
fn roundtrip_mov_mem_rax_rbx8_minus_16() {
    // [rax + rbx*8 - 16]
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rax), Some(GPRs::Rbx), Some(8), -16),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x44, 0xD8, 0xF0]),
    );
}

#[test]
fn roundtrip_mov_mem_rbp_rbx4_plus_32() {
    // [rbp + rbx*4 + 32]
    roundtrip(
        &mk_instr2(
            MnemonicKind::Mov,
            mem_exp(Some(GPRs::Rbp), Some(GPRs::Rbx), Some(4), 32),
            reg(GPRs::Rax),
        ),
        Some(&[0x48, 0x89, 0x44, 0x9D, 0x20]),
    );
}

// =========================================================================
// Jumps: JMP rel8/rel32, Jcc rel8/rel32
// =========================================================================

fn roundtrip_jump(instr: &Instruction, bytes: &[u8]) {
    let mut emit = Emitter::new();
    let mut relocs = RelocTable::new();
    let label_offsets = HashMap::new();
    encode_instruction(instr, &mut emit, &mut relocs, &label_offsets)
        .unwrap_or_else(|e| panic!("encode failed for {:?}: {}", instr, e));
    assert_eq!(emit.buf.as_slice(), bytes, "byte mismatch for {:?}", instr);

    let decoded = disasm::disassemble_all(bytes).unwrap_or_else(|e| panic!("decode failed: {}", e));
    assert_eq!(decoded.len(), 1);
    assert_eq!(decoded[0].instruction.mnemonic, instr.mnemonic);
    assert_eq!(decoded[0].instruction.operands[0], instr.operands[0]);
}

#[test]
fn roundtrip_jmp_rel8_forward() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jmp,
            operands: vec![imm(0x42)],
        },
        &[0xEB, 0x42],
    );
}

#[test]
fn roundtrip_jmp_rel8_backward() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jmp,
            operands: vec![imm(-0x20)],
        },
        &[0xEB, 0xE0],
    );
}

#[test]
fn roundtrip_jmp_rel32() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jmp,
            operands: vec![imm(0x1234)],
        },
        &[0xE9, 0x34, 0x12, 0x00, 0x00],
    );
}

#[test]
fn roundtrip_je_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Je,
            operands: vec![imm(0x7F)],
        },
        &[0x74, 0x7F],
    );
}

#[test]
fn roundtrip_je_rel8_backward() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Je,
            operands: vec![imm(-128)],
        },
        &[0x74, 0x80],
    );
}

#[test]
fn roundtrip_je_rel32() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Je,
            operands: vec![imm(0x100)],
        },
        &[0x0F, 0x84, 0x00, 0x01, 0x00, 0x00],
    );
}

#[test]
fn roundtrip_jo_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jo,
            operands: vec![imm(0x10)],
        },
        &[0x70, 0x10],
    );
}

#[test]
fn roundtrip_jno_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jno,
            operands: vec![imm(0x20)],
        },
        &[0x71, 0x20],
    );
}

#[test]
fn roundtrip_js_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Js,
            operands: vec![imm(0x30)],
        },
        &[0x78, 0x30],
    );
}

#[test]
fn roundtrip_jns_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jns,
            operands: vec![imm(0x40)],
        },
        &[0x79, 0x40],
    );
}

#[test]
fn roundtrip_jp_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jp,
            operands: vec![imm(0x50)],
        },
        &[0x7A, 0x50],
    );
}

#[test]
fn roundtrip_jnp_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jnp,
            operands: vec![imm(0x60)],
        },
        &[0x7B, 0x60],
    );
}

#[test]
fn roundtrip_jo_rel32() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jo,
            operands: vec![imm(0x200)],
        },
        &[0x0F, 0x80, 0x00, 0x02, 0x00, 0x00],
    );
}

#[test]
fn roundtrip_jb_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jb,
            operands: vec![imm(0x7F)],
        },
        &[0x72, 0x7F],
    );
}

#[test]
fn roundtrip_jae_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jae,
            operands: vec![imm(0x10)],
        },
        &[0x73, 0x10],
    );
}

#[test]
fn roundtrip_jne_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jne,
            operands: vec![imm(0x20)],
        },
        &[0x75, 0x20],
    );
}

#[test]
fn roundtrip_jbe_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jbe,
            operands: vec![imm(0x30)],
        },
        &[0x76, 0x30],
    );
}

#[test]
fn roundtrip_ja_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Ja,
            operands: vec![imm(0x40)],
        },
        &[0x77, 0x40],
    );
}

#[test]
fn roundtrip_jg_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jg,
            operands: vec![imm(0x50)],
        },
        &[0x7F, 0x50],
    );
}

#[test]
fn roundtrip_jge_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jge,
            operands: vec![imm(0x60)],
        },
        &[0x7D, 0x60],
    );
}

#[test]
fn roundtrip_jl_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jl,
            operands: vec![imm(0x70)],
        },
        &[0x7C, 0x70],
    );
}

#[test]
fn roundtrip_jle_rel8() {
    roundtrip_jump(
        &Instruction {
            mnemonic: MnemonicKind::Jle,
            operands: vec![imm(0x7F)],
        },
        &[0x7E, 0x7F],
    );
}

// =========================================================================
// Negative tests: what decode should REJECT
// =========================================================================

#[test]
fn decode_rejects_c7_with_nonzero_reg() {
    // 0xC7 ModRM(00, 1, 101) = 0x0D — reg=1, not /0
    // Should fail with UnknownOpcode (not silently decode as MOV [mem], imm32)
    let bytes = &[
        0x48, 0xC7, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00,
    ];
    let result = disasm::disassemble_all(bytes);
    assert!(result.is_err(), "0xC7 with reg!=0 should fail");
}
