use rasm_core::disasm;
use rasm_core::isa::{GPRs, Instruction, MnemonicKind, Operand, Registers};

fn decode_one(bytes: &[u8]) -> Instruction {
    let decoded = disasm::disassemble_all(bytes, None)
        .unwrap_or_else(|err| panic!("disasm failed for {bytes:02X?}: {err}"));
    assert_eq!(decoded.len(), 1, "expected exactly 1 decoded instruction");
    assert_eq!(
        decoded[0].bytes_len,
        bytes.len(),
        "decoded instruction length mismatch for {bytes:02X?}"
    );
    decoded[0].instruction.clone()
}

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_instr0(m: MnemonicKind) -> Instruction {
    Instruction {
        mnemonic: m,
        operands: vec![],
    }
}

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(rasm_core::isa::MemoryAddress {
        base: base.map(Registers::GPR),
        index,
        scale: scale.map(|s| match s {
            1 => rasm_core::isa::Scale::One,
            2 => rasm_core::isa::Scale::Two,
            4 => rasm_core::isa::Scale::Four,
            8 => rasm_core::isa::Scale::Eight,
            _ => panic!("invalid scale"),
        }),
        displacement,
        label: None,
    })
}

// =========================================================================
// PUSH
// =========================================================================

#[test]
fn push_rax() {
    let got = decode_one(&[0x50]);
    assert_eq!(got, mk_instr(MnemonicKind::Push, reg(GPRs::Rax)));
}

#[test]
fn push_r8() {
    let got = decode_one(&[0x41, 0x50]);
    assert_eq!(got, mk_instr(MnemonicKind::Push, reg(GPRs::R8)));
}

#[test]
fn push_r15() {
    let got = decode_one(&[0x41, 0x57]);
    assert_eq!(got, mk_instr(MnemonicKind::Push, reg(GPRs::R15)));
}

#[test]
fn push_imm32() {
    let got = decode_one(&[0x68, 0x2A, 0x00, 0x00, 0x00]);
    assert_eq!(got, mk_instr(MnemonicKind::Push, imm(0x2A)));
}

// =========================================================================
// POP
// =========================================================================

#[test]
fn pop_rax() {
    let got = decode_one(&[0x58]);
    assert_eq!(got, mk_instr(MnemonicKind::Pop, reg(GPRs::Rax)));
}

#[test]
fn pop_r8() {
    let got = decode_one(&[0x41, 0x58]);
    assert_eq!(got, mk_instr(MnemonicKind::Pop, reg(GPRs::R8)));
}

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

#[test]
fn mov_rax_imm64() {
    let got = decode_one(&[0x48, 0xB8, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
    assert_eq!(got, mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0x2A)));
}

#[test]
fn mov_r8_imm64() {
    let got = decode_one(&[0x49, 0xB8, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
    assert_eq!(got, mk_instr2(MnemonicKind::Mov, reg(GPRs::R8), imm(0xFF)));
}

#[test]
fn mov_rax_imm64_large() {
    let got = decode_one(&[0x48, 0xB8, 0x78, 0x56, 0x34, 0x12, 0x00, 0x00, 0x00, 0x00]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0x12345678))
    );
}

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

#[test]
fn mov_rax_rbx() {
    let got = decode_one(&[0x48, 0x89, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Mov, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn mov_rbx_rax() {
    let got = decode_one(&[0x48, 0x89, 0xC3]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), reg(GPRs::Rax))
    );
}

#[test]
fn mov_r8_r9() {
    let got = decode_one(&[0x4D, 0x89, 0xC8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Mov, reg(GPRs::R8), reg(GPRs::R9))
    );
}

// =========================================================================
// RET / NOP / SYSCALL / CQO
// =========================================================================

#[test]
fn ret() {
    let got = decode_one(&[0xC3]);
    assert_eq!(got, mk_instr0(MnemonicKind::Ret));
}

#[test]
fn nop() {
    let got = decode_one(&[0x90]);
    assert_eq!(got, mk_instr0(MnemonicKind::Nop));
}

#[test]
fn syscall() {
    let got = decode_one(&[0x0F, 0x05]);
    assert_eq!(got, mk_instr0(MnemonicKind::Syscall));
}

#[test]
fn cqo() {
    let got = decode_one(&[0x48, 0x99]);
    assert_eq!(got, mk_instr0(MnemonicKind::Cqo));
}

// =========================================================================
// ALU: ADD / SUB / XOR / AND / OR / CMP
// =========================================================================

#[test]
fn add_rax_rbx() {
    let got = decode_one(&[0x48, 0x01, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Add, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn add_r8_r9() {
    let got = decode_one(&[0x4D, 0x01, 0xC8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Add, reg(GPRs::R8), reg(GPRs::R9))
    );
}

#[test]
fn sub_rax_rbx() {
    let got = decode_one(&[0x48, 0x29, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Sub, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn xor_rax_rax() {
    let got = decode_one(&[0x48, 0x31, 0xC0]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Xor, reg(GPRs::Rax), reg(GPRs::Rax))
    );
}

#[test]
fn and_rax_rbx() {
    let got = decode_one(&[0x48, 0x21, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::And, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn or_rax_rbx() {
    let got = decode_one(&[0x48, 0x09, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Or, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn cmp_rax_rbx() {
    let got = decode_one(&[0x48, 0x39, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Cmp, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

// =========================================================================
// TEST
// =========================================================================

#[test]
fn test_rax_rbx() {
    let got = decode_one(&[0x48, 0x85, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Test, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

// =========================================================================
// Унарные: INC / DEC / NEG / NOT / MUL / DIV / IDIV / IMUL
// =========================================================================

#[test]
fn inc_rax() {
    let got = decode_one(&[0x48, 0xFF, 0xC0]);
    assert_eq!(got, mk_instr(MnemonicKind::Inc, reg(GPRs::Rax)));
}

#[test]
fn dec_rax() {
    let got = decode_one(&[0x48, 0xFF, 0xC8]);
    assert_eq!(got, mk_instr(MnemonicKind::Dec, reg(GPRs::Rax)));
}

#[test]
fn not_rax() {
    let got = decode_one(&[0x48, 0xF7, 0xD0]);
    assert_eq!(got, mk_instr(MnemonicKind::Not, reg(GPRs::Rax)));
}

#[test]
fn neg_rax() {
    let got = decode_one(&[0x48, 0xF7, 0xD8]);
    assert_eq!(got, mk_instr(MnemonicKind::Neg, reg(GPRs::Rax)));
}

#[test]
fn mul_rbx() {
    let got = decode_one(&[0x48, 0xF7, 0xE3]);
    assert_eq!(got, mk_instr(MnemonicKind::Mul, reg(GPRs::Rbx)));
}

#[test]
fn imul_one_op() {
    let got = decode_one(&[0x48, 0xF7, 0xE8]);
    assert_eq!(got, mk_instr(MnemonicKind::Imul, reg(GPRs::Rax)));
}

#[test]
fn div_rbx() {
    let got = decode_one(&[0x48, 0xF7, 0xF3]);
    assert_eq!(got, mk_instr(MnemonicKind::Div, reg(GPRs::Rbx)));
}

#[test]
fn idiv_rbx() {
    let got = decode_one(&[0x48, 0xF7, 0xFB]);
    assert_eq!(got, mk_instr(MnemonicKind::Idiv, reg(GPRs::Rbx)));
}

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

#[test]
fn imul_rax_rbx() {
    // imul rax, rbx  →  dst=Rax(0), src=Rbx(3) → ModRM(11, 0, 3) = 0xC3
    let got = decode_one(&[0x48, 0x0F, 0xAF, 0xC3]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Imul, reg(GPRs::Rax), reg(GPRs::Rbx))
    );
}

#[test]
fn imul_rbx_rax() {
    // imul rbx, rax  →  dst=Rbx(3), src=Rax(0) → ModRM(11, 3, 0) = 0xD8
    let got = decode_one(&[0x48, 0x0F, 0xAF, 0xD8]);
    assert_eq!(
        got,
        mk_instr2(MnemonicKind::Imul, reg(GPRs::Rbx), reg(GPRs::Rax))
    );
}

#[test]
fn imul_rax_rbx_imm() {
    let got = decode_one(&[0x48, 0x69, 0xC3, 0x2A, 0x00, 0x00, 0x00]);
    assert_eq!(
        got,
        Instruction {
            mnemonic: MnemonicKind::Imul,
            operands: vec![reg(GPRs::Rax), reg(GPRs::Rbx), imm(0x2A)],
        }
    );
}

// =========================================================================
// LEA
// =========================================================================

#[test]
fn lea_rax_rbx() {
    let got = decode_one(&[0x48, 0x8D, 0x03]);
    assert_eq!(
        got,
        mk_instr2(
            MnemonicKind::Lea,
            reg(GPRs::Rax),
            mem_exp(Some(GPRs::Rbx), None, None, 0)
        )
    );
}

// =========================================================================
// SHL / SHR / SAR
// =========================================================================

#[test]
fn shl_rax_1() {
    let got = decode_one(&[0x48, 0xD1, 0xE0]);
    assert_eq!(got, mk_instr2(MnemonicKind::Shl, reg(GPRs::Rax), imm(1)));
}

#[test]
fn shl_rax_imm8() {
    let got = decode_one(&[0x48, 0xC1, 0xE0, 0x03]);
    assert_eq!(got, mk_instr2(MnemonicKind::Shl, reg(GPRs::Rax), imm(3)));
}

#[test]
fn shl_rax_cl() {
    let got = decode_one(&[0x48, 0xD3, 0xE0]);
    assert_eq!(
        got,
        mk_instr2(
            MnemonicKind::Shl,
            reg(GPRs::Rax),
            Operand::Register(Registers::GPR(GPRs::Rcx))
        )
    );
}

#[test]
fn shr_rax_1() {
    let got = decode_one(&[0x48, 0xD1, 0xE8]);
    assert_eq!(got, mk_instr2(MnemonicKind::Shr, reg(GPRs::Rax), imm(1)));
}

#[test]
fn sar_rax_1() {
    let got = decode_one(&[0x48, 0xD1, 0xF8]);
    assert_eq!(got, mk_instr2(MnemonicKind::Sar, reg(GPRs::Rax), imm(1)));
}

// =========================================================================
// Условные прыжки (Operand::Immediate = rel32)
// =========================================================================

#[test]
fn jmp_rel32() {
    let decoded = disasm::disassemble_all(&[0xE9, 0x00, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jmp);
    assert_eq!(decoded[0].instruction.operands[0], imm(0));
}

#[test]
fn call_rel32() {
    let decoded = disasm::disassemble_all(&[0xE8, 0x00, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Call);
    assert_eq!(decoded[0].instruction.operands[0], imm(0));
}

#[test]
fn je_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x84, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Je);
}

#[test]
fn jne_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x85, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jne);
}

#[test]
fn jg_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x8F, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jg);
}

#[test]
fn jl_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x8C, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jl);
}

// =========================================================================
// Jcc rel8 (0x70-0x7F) и JMP rel8 (0xEB)
// =========================================================================

#[test]
fn jmp_rel8_forward() {
    let decoded = disasm::disassemble_all(&[0xEB, 0x2A], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jmp);
    assert_eq!(decoded[0].instruction.operands[0], imm(0x2A));
}

#[test]
fn jmp_rel8_backward() {
    let decoded = disasm::disassemble_all(&[0xEB, 0xD6], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jmp);
    assert_eq!(decoded[0].instruction.operands[0], imm(-0x2A));
}

#[test]
fn jo_rel8() {
    let decoded = disasm::disassemble_all(&[0x70, 0x01], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jo);
}

#[test]
fn jno_rel8() {
    let decoded = disasm::disassemble_all(&[0x71, 0x01], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jno);
}

#[test]
fn jb_rel8() {
    let decoded = disasm::disassemble_all(&[0x72, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jb);
}

#[test]
fn jae_rel8() {
    let decoded = disasm::disassemble_all(&[0x73, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jae);
}

#[test]
fn je_rel8() {
    let decoded = disasm::disassemble_all(&[0x74, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Je);
}

#[test]
fn jne_rel8() {
    let decoded = disasm::disassemble_all(&[0x75, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jne);
}

#[test]
fn jbe_rel8() {
    let decoded = disasm::disassemble_all(&[0x76, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jbe);
}

#[test]
fn ja_rel8() {
    let decoded = disasm::disassemble_all(&[0x77, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Ja);
}

#[test]
fn js_rel8() {
    let decoded = disasm::disassemble_all(&[0x78, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Js);
}

#[test]
fn jns_rel8() {
    let decoded = disasm::disassemble_all(&[0x79, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jns);
}

#[test]
fn jp_rel8() {
    let decoded = disasm::disassemble_all(&[0x7A, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jp);
}

#[test]
fn jnp_rel8() {
    let decoded = disasm::disassemble_all(&[0x7B, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jnp);
}

#[test]
fn jl_rel8() {
    let decoded = disasm::disassemble_all(&[0x7C, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jl);
}

#[test]
fn jge_rel8() {
    let decoded = disasm::disassemble_all(&[0x7D, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jge);
}

#[test]
fn jle_rel8() {
    let decoded = disasm::disassemble_all(&[0x7E, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jle);
}

#[test]
fn jg_rel8() {
    let decoded = disasm::disassemble_all(&[0x7F, 0x42], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jg);
}

// =========================================================================
// Jcc rel32: missing entries (0x0F 0x80-0x81, 0x88-0x8B)
// =========================================================================

#[test]
fn jo_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x80, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jo);
}

#[test]
fn jno_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x81, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jno);
}

#[test]
fn js_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x88, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Js);
}

#[test]
fn jns_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x89, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jns);
}

#[test]
fn jp_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x8A, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jp);
}

#[test]
fn jnp_rel32() {
    let decoded = disasm::disassemble_all(&[0x0F, 0x8B, 0x2A, 0x00, 0x00, 0x00], None).unwrap();
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Jnp);
}

// =========================================================================
// Последовательности
// =========================================================================

#[test]
fn push_pop_ret_sequence() {
    let decoded = disasm::disassemble_all(&[0x50, 0x51, 0x58, 0x59, 0xC3], None).unwrap();
    assert_eq!(decoded.len(), 5);
    assert_eq!(decoded[0].instruction.mnemonic, MnemonicKind::Push);
    assert_eq!(decoded[1].instruction.mnemonic, MnemonicKind::Push);
    assert_eq!(decoded[2].instruction.mnemonic, MnemonicKind::Pop);
    assert_eq!(decoded[3].instruction.mnemonic, MnemonicKind::Pop);
    assert_eq!(decoded[4].instruction.mnemonic, MnemonicKind::Ret);
}

// =========================================================================
// Memory addressing
// =========================================================================

#[test]
fn mov_mem_rbp_rax() {
    let got = decode_one(&[0x48, 0x89, 0x45, 0x00]);
    assert_eq!(got.mnemonic, MnemonicKind::Mov);
    if let Operand::Memory(m) = &got.operands[0] {
        assert_eq!(m.base, Some(Registers::GPR(GPRs::Rbp)));
        assert_eq!(m.displacement, 0);
    } else {
        panic!("expected memory operand");
    }
    assert_eq!(got.operands[1], reg(GPRs::Rax));
}

#[test]
fn mov_mem_rbp_plus_8_rax() {
    let got = decode_one(&[0x48, 0x89, 0x45, 0x08]);
    assert_eq!(got.mnemonic, MnemonicKind::Mov);
    if let Operand::Memory(m) = &got.operands[0] {
        assert_eq!(m.base, Some(Registers::GPR(GPRs::Rbp)));
        assert_eq!(m.displacement, 8);
    } else {
        panic!("expected memory operand");
    }
}

#[test]
fn mov_rax_mem_rbx() {
    let got = decode_one(&[0x48, 0x8B, 0x03]);
    assert_eq!(got.mnemonic, MnemonicKind::Mov);
    assert_eq!(got.operands[0], reg(GPRs::Rax));
    if let Operand::Memory(m) = &got.operands[1] {
        assert_eq!(m.base, Some(Registers::GPR(GPRs::Rbx)));
        assert_eq!(m.displacement, 0);
    } else {
        panic!("expected memory operand");
    }
}

// =========================================================================
// Format test
// =========================================================================

#[test]
fn format_mov_rax_imm() {
    let bytes = &[0x48, 0xB8, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert_eq!(lines.len(), 1);
    assert!(lines[0].contains("mov"));
    assert!(lines[0].contains("rax"));
}

// =========================================================================
// Format: memory with negative displacement
// =========================================================================

#[test]
fn format_memory_negative_disp() {
    // MOV [rbp - 0x8], rax  → 48 89 45 F8
    let bytes = &[0x48, 0x89, 0x45, 0xF8];
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0], "    mov [rbp - 0x8], rax");
}

#[test]
fn format_memory_positive_disp() {
    // MOV [rbp + 0x8], rax  → 48 89 45 08
    let bytes = &[0x48, 0x89, 0x45, 0x08];
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert_eq!(lines.len(), 1);
    assert_eq!(lines[0], "    mov [rbp + 0x8], rax");
}

// =========================================================================
// Format: labels for jump targets
// =========================================================================

#[test]
fn disasm_jmp_forward_uses_label() {
    // jmp rel32=12 → E9 0C 00 00 00 (5 bytes)
    // followed by nop at +5, nop at +6, ... up to target offset 17
    // target = 12 → next_instruction_offset=5, abs=5+12=17
    let bytes = &[
        0xE9, 0x0C, 0x00, 0x00, 0x00, // jmp past 12 bytes
        0x90, 0x90, 0x90, 0x90, 0x90, // offsets 5-9: 5 nops
        0x90, 0x90, 0x90, 0x90, 0x90, // offsets 10-14
        0x90, 0x90, // offsets 15-16
        0x90,
    ]; // offset 17: target
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert!(lines[0].contains("jmp"));
    assert!(lines[0].contains("loc_"));
    assert!(lines.iter().any(|l| l.starts_with("loc_")));
}

#[test]
fn disasm_jcc_forward_uses_label() {
    // je rel32=6 → 0F 84 06 00 00 00 (6 bytes)
    // next_offset=6, target=6, abs=6+6=12
    let bytes = &[
        0x0F, 0x84, 0x06, 0x00, 0x00, 0x00, // je +6
        0x90, 0x90, 0x90, 0x90, 0x90, 0x90, // 6 nops at offsets 6-11
        0x90,
    ]; // nop at offset 12 = target
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert!(lines[0].contains("je"));
    assert!(lines[0].contains("loc_"));
    assert!(lines.iter().any(|l| l.starts_with("loc_")));
}

#[test]
fn disasm_call_forward_uses_label() {
    // call rel32=5 → E8 05 00 00 00 (5 bytes)
    // next_offset=5, target=5, abs=5+5=10
    let bytes = &[
        0xE8, 0x05, 0x00, 0x00, 0x00, // call +5
        0x90, 0x90, 0x90, 0x90, 0x90, // 5 nops at offsets 5-9
        0x90,
    ]; // nop at offset 10 = target
    let lines = disasm::disassemble_to_text(bytes).unwrap();
    assert!(lines[0].contains("call"));
    assert!(lines[0].contains("loc_"));
    assert!(lines.iter().any(|l| l.starts_with("loc_")));
}

// =========================================================================
// Format: all mnemonics via direct format_instruction calls
// =========================================================================

use rasm_core::disasm::format::format_instruction;

fn fmt_instr(m: MnemonicKind, ops: Vec<Operand>) -> String {
    format_instruction(&Instruction {
        mnemonic: m,
        operands: ops,
    })
}

fn fmt_instr0(m: MnemonicKind) -> String {
    fmt_instr(m, vec![])
}

fn fmt_instr1(m: MnemonicKind, op: Operand) -> String {
    fmt_instr(m, vec![op])
}

fn fmt_instr2(m: MnemonicKind, op1: Operand, op2: Operand) -> String {
    fmt_instr(m, vec![op1, op2])
}

fn lbl(id: u32) -> Operand {
    Operand::Label(rasm_core::str_pool::StrId(id))
}

fn xmm_reg(n: u8) -> Operand {
    use rasm_core::isa::XMMRs::*;
    Operand::Register(Registers::XMM(match n {
        0 => Xmm0,
        1 => Xmm1,
        2 => Xmm2,
        3 => Xmm3,
        4 => Xmm4,
        5 => Xmm5,
        6 => Xmm6,
        7 => Xmm7,
        8 => Xmm8,
        9 => Xmm9,
        10 => Xmm10,
        11 => Xmm11,
        12 => Xmm12,
        13 => Xmm13,
        14 => Xmm14,
        15 => Xmm15,
        _ => panic!("bad xmm"),
    }))
}

fn sr_reg(s: &str) -> Operand {
    use rasm_core::isa::SegmentRegisters::*;
    Operand::Register(Registers::SR(match s {
        "fs" => Fs,
        "gs" => Gs,
        "cs" => Cs,
        "ss" => Ss,
        "ds" => Ds,
        "es" => Es,
        _ => panic!("bad sr"),
    }))
}

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

#[test]
fn format_mnemonic_push() {
    assert_eq!(
        fmt_instr1(MnemonicKind::Push, reg(GPRs::Rax)),
        "    push rax"
    );
}

#[test]
fn format_mnemonic_pop() {
    assert_eq!(fmt_instr1(MnemonicKind::Pop, reg(GPRs::Rax)), "    pop rax");
}

#[test]
fn format_mnemonic_add() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Add, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    add rax, rbx"
    );
}

#[test]
fn format_mnemonic_sub() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Sub, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    sub rax, rbx"
    );
}

#[test]
fn format_mnemonic_and() {
    assert_eq!(
        fmt_instr2(MnemonicKind::And, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    and rax, rbx"
    );
}

#[test]
fn format_mnemonic_or() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Or, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    or rax, rbx"
    );
}

#[test]
fn format_mnemonic_xor() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Xor, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    xor rax, rbx"
    );
}

#[test]
fn format_mnemonic_cmp() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Cmp, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    cmp rax, rbx"
    );
}

#[test]
fn format_mnemonic_test() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Test, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    test rax, rbx"
    );
}

#[test]
fn format_mnemonic_inc() {
    assert_eq!(fmt_instr1(MnemonicKind::Inc, reg(GPRs::Rax)), "    inc rax");
}

#[test]
fn format_mnemonic_dec() {
    assert_eq!(fmt_instr1(MnemonicKind::Dec, reg(GPRs::Rax)), "    dec rax");
}

#[test]
fn format_mnemonic_neg() {
    assert_eq!(fmt_instr1(MnemonicKind::Neg, reg(GPRs::Rax)), "    neg rax");
}

#[test]
fn format_mnemonic_not() {
    assert_eq!(fmt_instr1(MnemonicKind::Not, reg(GPRs::Rax)), "    not rax");
}

#[test]
fn format_mnemonic_mul() {
    assert_eq!(fmt_instr1(MnemonicKind::Mul, reg(GPRs::Rbx)), "    mul rbx");
}

#[test]
fn format_mnemonic_div() {
    assert_eq!(fmt_instr1(MnemonicKind::Div, reg(GPRs::Rbx)), "    div rbx");
}

#[test]
fn format_mnemonic_idiv() {
    assert_eq!(
        fmt_instr1(MnemonicKind::Idiv, reg(GPRs::Rbx)),
        "    idiv rbx"
    );
}

#[test]
fn format_mnemonic_imul() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Imul, reg(GPRs::Rax), reg(GPRs::Rbx)),
        "    imul rax, rbx"
    );
}

#[test]
fn format_mnemonic_lea() {
    let m = mem_full(Some(GPRs::Rbx), None, None, 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Lea, reg(GPRs::Rax), m),
        "    lea rax, [rbx]"
    );
}

#[test]
fn format_mnemonic_shl() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Shl, reg(GPRs::Rax), imm(1)),
        "    shl rax, 0x1"
    );
}

#[test]
fn format_mnemonic_shr() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Shr, reg(GPRs::Rax), imm(1)),
        "    shr rax, 0x1"
    );
}

#[test]
fn format_mnemonic_sar() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Sar, reg(GPRs::Rax), imm(1)),
        "    sar rax, 0x1"
    );
}

#[test]
fn format_mnemonic_cqo() {
    assert_eq!(fmt_instr0(MnemonicKind::Cqo), "    cqo");
}

#[test]
fn format_mnemonic_nop() {
    assert_eq!(fmt_instr0(MnemonicKind::Nop), "    nop");
}

#[test]
fn format_mnemonic_ret() {
    assert_eq!(fmt_instr0(MnemonicKind::Ret), "    ret");
}

#[test]
fn format_mnemonic_syscall() {
    assert_eq!(fmt_instr0(MnemonicKind::Syscall), "    syscall");
}

#[test]
fn format_mnemonic_jmp() {
    assert_eq!(fmt_instr1(MnemonicKind::Jmp, imm(0)), "    jmp 0x0");
}

#[test]
fn format_mnemonic_jne() {
    assert_eq!(fmt_instr1(MnemonicKind::Jne, imm(0)), "    jne 0x0");
}

#[test]
fn format_mnemonic_jg() {
    assert_eq!(fmt_instr1(MnemonicKind::Jg, imm(0)), "    jg 0x0");
}

#[test]
fn format_mnemonic_jge() {
    assert_eq!(fmt_instr1(MnemonicKind::Jge, imm(0)), "    jge 0x0");
}

#[test]
fn format_mnemonic_jl() {
    assert_eq!(fmt_instr1(MnemonicKind::Jl, imm(0)), "    jl 0x0");
}

#[test]
fn format_mnemonic_jle() {
    assert_eq!(fmt_instr1(MnemonicKind::Jle, imm(0)), "    jle 0x0");
}

#[test]
fn format_mnemonic_ja() {
    assert_eq!(fmt_instr1(MnemonicKind::Ja, imm(0)), "    ja 0x0");
}

#[test]
fn format_mnemonic_jae() {
    assert_eq!(fmt_instr1(MnemonicKind::Jae, imm(0)), "    jae 0x0");
}

#[test]
fn format_mnemonic_jb() {
    assert_eq!(fmt_instr1(MnemonicKind::Jb, imm(0)), "    jb 0x0");
}

#[test]
fn format_mnemonic_jbe() {
    assert_eq!(fmt_instr1(MnemonicKind::Jbe, imm(0)), "    jbe 0x0");
}

#[test]
fn format_mnemonic_jo() {
    assert_eq!(fmt_instr1(MnemonicKind::Jo, imm(0)), "    jo 0x0");
}

#[test]
fn format_mnemonic_jno() {
    assert_eq!(fmt_instr1(MnemonicKind::Jno, imm(0)), "    jno 0x0");
}

#[test]
fn format_mnemonic_js() {
    assert_eq!(fmt_instr1(MnemonicKind::Js, imm(0)), "    js 0x0");
}

#[test]
fn format_mnemonic_jns() {
    assert_eq!(fmt_instr1(MnemonicKind::Jns, imm(0)), "    jns 0x0");
}

#[test]
fn format_mnemonic_jp() {
    assert_eq!(fmt_instr1(MnemonicKind::Jp, imm(0)), "    jp 0x0");
}

#[test]
fn format_mnemonic_jnp() {
    assert_eq!(fmt_instr1(MnemonicKind::Jnp, imm(0)), "    jnp 0x0");
}

#[test]
fn format_mnemonic_call() {
    assert_eq!(fmt_instr1(MnemonicKind::Call, imm(0)), "    call 0x0");
}

// =========================================================================
// Format: all GPRs
// =========================================================================

#[test]
fn format_gpr_rcx() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rcx), reg(GPRs::Rax)),
        "    mov rcx, rax"
    );
}

#[test]
fn format_gpr_rdx() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rdx), reg(GPRs::Rax)),
        "    mov rdx, rax"
    );
}

#[test]
fn format_gpr_rbx() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), reg(GPRs::Rax)),
        "    mov rbx, rax"
    );
}

#[test]
fn format_gpr_rsp() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rsp), reg(GPRs::Rax)),
        "    mov rsp, rax"
    );
}

#[test]
fn format_gpr_rbp() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbp), reg(GPRs::Rax)),
        "    mov rbp, rax"
    );
}

#[test]
fn format_gpr_rsi() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rsi), reg(GPRs::Rax)),
        "    mov rsi, rax"
    );
}

#[test]
fn format_gpr_rdi() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rdi), reg(GPRs::Rax)),
        "    mov rdi, rax"
    );
}

#[test]
fn format_gpr_r8() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R8), reg(GPRs::Rax)),
        "    mov r8, rax"
    );
}

#[test]
fn format_gpr_r9() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R9), reg(GPRs::Rax)),
        "    mov r9, rax"
    );
}

#[test]
fn format_gpr_r10() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R10), reg(GPRs::Rax)),
        "    mov r10, rax"
    );
}

#[test]
fn format_gpr_r11() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R11), reg(GPRs::Rax)),
        "    mov r11, rax"
    );
}

#[test]
fn format_gpr_r12() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R12), reg(GPRs::Rax)),
        "    mov r12, rax"
    );
}

#[test]
fn format_gpr_r13() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R13), reg(GPRs::Rax)),
        "    mov r13, rax"
    );
}

#[test]
fn format_gpr_r14() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R14), reg(GPRs::Rax)),
        "    mov r14, rax"
    );
}

#[test]
fn format_gpr_r15() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::R15), reg(GPRs::Rax)),
        "    mov r15, rax"
    );
}

// =========================================================================
// Format: special registers (XMM, SR, Rflags, Rip)
// =========================================================================

#[test]
fn format_xmm_all() {
    for n in 0..=15 {
        let s = fmt_instr1(MnemonicKind::Mov, xmm_reg(n));
        assert!(s.contains(&format!("xmm{}", n)));
    }
}

#[test]
fn format_sreg_all() {
    for s in &["fs", "gs", "cs", "ss", "ds", "es"] {
        let line = fmt_instr1(MnemonicKind::Mov, sr_reg(s));
        assert!(
            line.contains(s),
            "expected segment register {s} in '{line}'"
        );
    }
}

#[test]
fn format_rflags() {
    let line = fmt_instr1(MnemonicKind::Push, Operand::Register(Registers::Rflags));
    assert_eq!(line, "    push rflags");
}

#[test]
fn format_rip() {
    let line = fmt_instr1(MnemonicKind::Mov, Operand::Register(Registers::Rip));
    assert_eq!(line, "    mov rip");
}

// =========================================================================
// Format: immediate edge cases
// =========================================================================

#[test]
fn format_immediate_negative() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(-1)),
        "    mov rax, -0x1"
    );
}

#[test]
fn format_immediate_negative_large() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(-256)),
        "    mov rax, -0x100"
    );
}

#[test]
fn format_immediate_zero() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0)),
        "    mov rax, 0x0"
    );
}

#[test]
fn format_immediate_large() {
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), imm(0xDEADBEEF)),
        "    mov rax, 0xDEADBEEF"
    );
}

// =========================================================================
// Format: memory with only displacement (no base/index)
// =========================================================================

#[test]
fn format_memory_only_displacement_positive() {
    let m = mem_full(None, None, None, 0x42, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [0x42]"
    );
}

#[test]
fn format_memory_only_displacement_negative() {
    let m = mem_full(None, None, None, -0x20, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [-0x20]"
    );
}

#[test]
fn format_memory_only_displacement_zero() {
    let m = mem_full(None, None, None, 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [0x0]"
    );
}

// =========================================================================
// Format: memory with scale factor
// =========================================================================

#[test]
fn format_memory_scale_one() {
    let m = mem_full(None, Some(GPRs::Rax), Some(1), 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), m),
        "    mov rbx, [rax]"
    );
}

#[test]
fn format_memory_scale_two() {
    let m = mem_full(None, Some(GPRs::Rax), Some(2), 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), m),
        "    mov rbx, [rax*2]"
    );
}

#[test]
fn format_memory_scale_four() {
    let m = mem_full(None, Some(GPRs::Rax), Some(4), 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), m),
        "    mov rbx, [rax*4]"
    );
}

#[test]
fn format_memory_scale_eight() {
    let m = mem_full(None, Some(GPRs::Rax), Some(8), 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rbx), m),
        "    mov rbx, [rax*8]"
    );
}

#[test]
fn format_memory_base_index_scale() {
    let m = mem_full(Some(GPRs::Rbp), Some(GPRs::Rbx), Some(4), 0, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [rbp rbx*4]"
    );
}

#[test]
fn format_memory_base_index_scale_disp() {
    let m = mem_full(Some(GPRs::Rbp), Some(GPRs::Rbx), Some(4), 0x10, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [rbp rbx*4 + 0x10]"
    );
}

// =========================================================================
// Format: memory with label
// =========================================================================

#[test]
fn format_memory_label() {
    let m = mem_full(None, None, None, 0, Some(42));
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [lbl_42]"
    );
}

#[test]
fn format_memory_label_no_label_shown_in_immediate() {
    // Operand::Label directly (not through MemoryAddress)
    assert_eq!(fmt_instr1(MnemonicKind::Jmp, lbl(5)), "    jmp lbl_5");
}

// =========================================================================
// Format: memory zero base register
// =========================================================================

#[test]
fn format_memory_scale_negative_disp() {
    let m = mem_full(Some(GPRs::Rbp), Some(GPRs::Rbx), Some(8), -0x10, None);
    assert_eq!(
        fmt_instr2(MnemonicKind::Mov, reg(GPRs::Rax), m),
        "    mov rax, [rbp rbx*8 - 0x10]"
    );
}

// =========================================================================
// Error display
// =========================================================================

#[test]
fn error_display_unknown_opcode() {
    let err = rasm_core::disasm::DisasmError::new(
        rasm_core::disasm::DisasmErrorKind::UnknownOpcode(0xC7, 0x3),
    );
    assert_eq!(err.to_string(), "unknown opcode 0xC7 at offset 0x3");
}

#[test]
fn error_display_truncated() {
    let err = rasm_core::disasm::DisasmError::new(
        rasm_core::disasm::DisasmErrorKind::TruncatedInstruction,
    );
    assert_eq!(
        err.to_string(),
        "truncated instruction — unexpected end of input"
    );
}

#[test]
fn error_display_invalid_modrm() {
    let err = rasm_core::disasm::DisasmError::new(rasm_core::disasm::DisasmErrorKind::InvalidModRM);
    assert_eq!(err.to_string(), "invalid ModRM byte");
}

#[test]
fn error_display_invalid_sib() {
    let err = rasm_core::disasm::DisasmError::new(rasm_core::disasm::DisasmErrorKind::InvalidSib);
    assert_eq!(err.to_string(), "invalid Sib byte");
}
