//! Тесты на весь пайплайн.
use crate::codegen_tests::helper::compile;

fn assert_code(source: &str, expected: &[u8]) {
    let output = compile(source).unwrap_or_else(|e| panic!("compile failed: {e}"));
    assert_eq!(output.code, expected, "wrong bytes for `{source}`");
}

fn assert_code_with_labels(source: &str, expected: &[u8], label_count: usize) {
    let output = compile(source).unwrap_or_else(|e| panic!("compile failed: {e}"));
    assert_eq!(output.code, expected, "wrong bytes for `{source}`");
    assert_eq!(output.label_offsets.len(), label_count);
}

#[test]
fn pipeline_nop_ret() {
    assert_code("nop\nret", &[0x90, 0xC3]);
}

#[test]
fn pipeline_push_pop_ret() {
    assert_code("push rax\npop rbx\nret", &[0x50, 0x5B, 0xC3]);
}

#[test]
fn pipeline_mov_imm() {
    assert_code(
        "mov rax, 0x2A",
        &[0x48, 0xB8, 0x2A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    );
}

#[test]
fn pipeline_add_regs() {
    assert_code("add rax, rbx", &[0x48, 0x01, 0xD8]);
}

#[test]
fn pipeline_sub_imm() {
    assert_code("sub rax, 0x10", &[0x48, 0x83, 0xE8, 0x10]);
}

#[test]
fn pipeline_xor_self() {
    assert_code("xor rax, rax", &[0x48, 0x31, 0xC0]);
}

#[test]
fn pipeline_syscall() {
    assert_code("syscall", &[0x0F, 0x05]);
}

#[test]
fn pipeline_cqo() {
    assert_code("cqo", &[0x48, 0x99]);
}

#[test]
fn pipeline_inc() {
    assert_code("inc rax", &[0x48, 0xFF, 0xC0]);
}

#[test]
fn pipeline_dec() {
    assert_code("dec rax", &[0x48, 0xFF, 0xC8]);
}

#[test]
fn pipeline_neg() {
    assert_code("neg rax", &[0x48, 0xF7, 0xD8]);
}

#[test]
fn pipeline_not() {
    assert_code("not rax", &[0x48, 0xF7, 0xD0]);
}

#[test]
fn pipeline_mul() {
    assert_code("mul rbx", &[0x48, 0xF7, 0xE3]);
}

#[test]
fn pipeline_div() {
    assert_code("div rbx", &[0x48, 0xF7, 0xF3]);
}

#[test]
fn pipeline_idiv() {
    assert_code("idiv rbx", &[0x48, 0xF7, 0xFB]);
}

#[test]
fn pipeline_imul_two_op() {
    assert_code("imul rax, rbx", &[0x48, 0x0F, 0xAF, 0xC3]);
}

#[test]
fn pipeline_shl_imm() {
    assert_code("shl rax, 3", &[0x48, 0xC1, 0xE0, 0x03]);
}

#[test]
fn pipeline_shl_rcx() {
    assert_code("shl rax, rcx", &[0x48, 0xD3, 0xE0]);
}

#[test]
fn pipeline_shr_imm() {
    assert_code("shr rax, 1", &[0x48, 0xD1, 0xE8]);
}

#[test]
fn pipeline_sar_imm() {
    assert_code("sar rax, 2", &[0x48, 0xC1, 0xF8, 0x02]);
}

#[test]
fn pipeline_lea() {
    assert_code("lea rax, [rbx]", &[0x48, 0x8D, 0x03]);
}

#[test]
fn pipeline_lea_with_offset() {
    assert_code("lea rax, [rbx + 0x10]", &[0x48, 0x8D, 0x43, 0x10]);
}

#[test]
fn pipeline_mov_memory_base() {
    assert_code("mov rax, [rbx]", &[0x48, 0x8B, 0x03]);
}

#[test]
fn pipeline_mov_memory_offset() {
    assert_code(
        "mov rax, [rbx + 0x100]",
        &[0x48, 0x8B, 0x83, 0x00, 0x01, 0x00, 0x00],
    );
}

#[test]
fn pipeline_mov_to_memory() {
    assert_code("mov [rbp - 0x8], rax", &[0x48, 0x89, 0x45, 0xF8]);
}

#[test]
fn pipeline_cmp() {
    assert_code("cmp rax, rbx", &[0x48, 0x39, 0xD8]);
}

#[test]
fn pipeline_test() {
    assert_code("test rax, rbx", &[0x48, 0x85, 0xD8]);
}

#[test]
fn pipeline_and() {
    assert_code("and rax, rbx", &[0x48, 0x21, 0xD8]);
}

#[test]
fn pipeline_or() {
    assert_code("or rax, rbx", &[0x48, 0x09, 0xD8]);
}

#[test]
fn pipeline_push_memory() {
    assert_code("push rax", &[0x50]);
}

// =========================================================================
// Directives (use db/dw/dd/dq — aliases that don't conflict with DataType keywords)
// =========================================================================

#[test]
fn directive_byte() {
    assert_code("db 0x48, 0xB8, 0x01", &[0x48, 0xB8, 0x01]);
}

#[test]
fn directive_word() {
    assert_code("dw 0x1234", &[0x34, 0x12]);
}

#[test]
fn directive_word_multiple() {
    assert_code("dw 0x1234, 0x5678", &[0x34, 0x12, 0x78, 0x56]);
}

#[test]
fn directive_dword() {
    assert_code("dd 0xDEADBEEF", &[0xEF, 0xBE, 0xAD, 0xDE]);
}

#[test]
fn directive_quad() {
    assert_code(
        "dq 0x0102030405060708",
        &[0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01],
    );
}

#[test]
fn directive_quad_zero() {
    assert_code("dq 0", &[0; 8]);
}

#[test]
fn directive_ascii() {
    assert_code(".ascii \"hello\"", b"hello");
}

#[test]
fn directive_ascii_empty() {
    assert_code(".ascii \"\"", b"");
}

#[test]
fn directive_ascii_with_code() {
    assert_code("nop\n.ascii \"abc\"\nret", &[0x90, b'a', b'b', b'c', 0xC3]);
}

#[test]
fn directive_align_text_nop() {
    assert_code("nop\n.align 4", &[0x90, 0x90, 0x90, 0x90]);
}

#[test]
fn directive_align_power_of_two() {
    assert_code("nop\n.align 16", &[0x90; 16]);
}

#[test]
fn directive_align_zero_is_noop() {
    assert_code("nop\n.align 0", &[0x90]);
}

#[test]
fn directive_align_one_is_noop() {
    assert_code("nop\n.align 1", &[0x90]);
}

// =========================================================================
// Labels
// =========================================================================

#[test]
fn pipeline_label_forward_jump() {
    assert_code_with_labels(
        "jmp target\nnop\nnop\ntarget:\nnop",
        &[0xE9, 0x02, 0x00, 0x00, 0x00, 0x90, 0x90, 0x90],
        1,
    );
}

#[test]
fn pipeline_label_backward_jump() {
    assert_code_with_labels(
        "loop:\nnop\njmp loop",
        &[0x90, 0xE9, 0xFA, 0xFF, 0xFF, 0xFF],
        1,
    );
}

#[test]
fn pipeline_label_call_forward() {
    assert_code(
        "call func\nnop\nfunc:\nret",
        &[0xE8, 0x01, 0x00, 0x00, 0x00, 0x90, 0xC3],
    );
}

#[test]
fn pipeline_label_je_forward() {
    assert_code(
        "je dest\nret\ndest:\nnop",
        &[0x0F, 0x84, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_label_jne_forward() {
    assert_code(
        "jne dest\nret\ndest:\nnop",
        &[0x0F, 0x85, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_multiple_labels() {
    let src = "start:\nnop\nend:\nret";
    let output = compile(src).unwrap();
    assert_eq!(output.code, &[0x90, 0xC3]);
    assert_eq!(output.label_offsets.len(), 2);
    assert_eq!(output.label_offsets.get("start"), Some(&0));
    assert_eq!(output.label_offsets.get("end"), Some(&1));
}

#[test]
fn pipeline_jge_label() {
    assert_code(
        "jge dest\nret\ndest:\nnop",
        &[0x0F, 0x8D, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jl_label() {
    assert_code(
        "jl dest\nret\ndest:\nnop",
        &[0x0F, 0x8C, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jle_label() {
    assert_code(
        "jle dest\nret\ndest:\nnop",
        &[0x0F, 0x8E, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_ja_label() {
    assert_code(
        "ja dest\nret\ndest:\nnop",
        &[0x0F, 0x87, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jae_label() {
    assert_code(
        "jae dest\nret\ndest:\nnop",
        &[0x0F, 0x83, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jb_label() {
    assert_code(
        "jb dest\nret\ndest:\nnop",
        &[0x0F, 0x82, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jbe_label() {
    assert_code(
        "jbe dest\nret\ndest:\nnop",
        &[0x0F, 0x86, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

#[test]
fn pipeline_jg_label() {
    assert_code(
        "jg dest\nret\ndest:\nnop",
        &[0x0F, 0x8F, 0x01, 0x00, 0x00, 0x00, 0xC3, 0x90],
    );
}

// =========================================================================
// Mixed directives and code
// =========================================================================

#[test]
fn directive_byte_then_code() {
    assert_code("db 0x90\nret", &[0x90, 0xC3]);
}

#[test]
fn directive_quad_then_push() {
    assert_code("dq 0\npush rax", &[0, 0, 0, 0, 0, 0, 0, 0, 0x50]);
}

#[test]
fn section_text_directive() {
    assert_code(".text\nnop\nret", &[0x90, 0xC3]);
}

#[test]
fn global_directive() {
    let output = compile(".global _start\n_start:\nnop").unwrap();
    assert_eq!(output.code, &[0x90]);
}

#[test]
fn extern_directive() {
    let output = compile(".extern printf\n_start:\nnop").unwrap();
    assert_eq!(output.code, &[0x90]);
}

#[test]
fn directive_data_section() {
    assert_code(
        ".data\ndq 0x42",
        &[0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
    );
}

#[test]
fn directive_bss_section() {
    assert_code(".bss\n.text\nnop", &[0x90]);
}

#[test]
fn directive_quad_symbol_label() {
    let output = compile("myvar:\ndq myvar").unwrap();
    assert_eq!(output.code.len(), 8);
}
