use rasm_core::{
    codegen::{
        jit::{JitExecutor, symbol::SymbolTable},
        reloc::RelocKind,
    },
    object::SymbolVisibility,
};

use super::helper::compile;

#[test]
fn import_directive_is_recorded_in_module() {
    let output = compile(
        r#"
            .import kernel32.dll WriteFile
            _start:
                mov rax, 0
                ret
        "#,
    )
    .expect("program must compile");

    assert_eq!(output.module.imports.len(), 1);
    assert_eq!(output.module.imports[0].module, "kernel32.dll");
    assert_eq!(output.module.imports[0].name, "WriteFile");
    assert_eq!(output.module.imports[0].alias, "WriteFile");

    let symbol = output
        .module
        .symbol("WriteFile")
        .expect("imported symbol must be present");
    assert_eq!(symbol.visibility, SymbolVisibility::External);
    assert!(symbol.section.is_none());
}

#[test]
fn call_to_import_keeps_relocation_without_undefined_label_error() {
    let output = compile(
        r#"
            .import kernel32.dll WriteFile
            _start:
                call WriteFile
                mov rax, 0
                ret
        "#,
    )
    .expect("program with imported call must compile");

    let reloc = output
        .module
        .relocations
        .iter()
        .find(|reloc| reloc.target == "WriteFile")
        .expect("imported call must leave relocation");

    assert_eq!(reloc.kind, RelocKind::Rel32);
}

extern "C" fn jit_test_return_42() -> i64 {
    42
}

#[test]
fn jit_can_call_registered_extern_symbol() {
    let output = compile(
        r#"
            .extern jit_test_return_42
            _start:
                call jit_test_return_42
                ret
        "#,
    )
    .expect("program with extern call must compile");

    let mut symbols = SymbolTable::new();
    symbols.register(
        "jit_test_return_42",
        jit_test_return_42 as *const () as usize,
    );
    let executor = JitExecutor::new(&output.module, &mut symbols, &[])
        .expect("JIT must resolve registered extern");

    let result = unsafe { executor.execute() };
    assert_eq!(result, 42);
}

#[cfg(target_os = "windows")]
#[test]
fn jit_can_call_windows_import() {
    let output = compile(
        r#"
            .import kernel32.dll GetCurrentProcessId
            _start:
                sub rsp, 40
                call GetCurrentProcessId
                add rsp, 40
                ret
        "#,
    )
    .expect("program with Windows import must compile");

    let mut symbols = SymbolTable::new();
    let executor = JitExecutor::new(&output.module, &mut symbols, &[])
        .expect("JIT must resolve kernel32 import");

    let result = unsafe { executor.execute() };
    assert!(result > 0);
}

#[cfg(target_os = "linux")]
#[test]
fn jit_can_call_linux_import() {
    let output = compile(
        r#"
            .import libc.so.6 getpid
            _start:
                sub rsp, 8
                call getpid
                add rsp, 8
                ret
        "#,
    )
    .expect("program with Linux import must compile");

    let mut symbols = SymbolTable::new();
    let executor =
        JitExecutor::new(&output.module, &mut symbols, &[]).expect("JIT must resolve libc import");

    let result = unsafe { executor.execute() };
    assert!(result > 0);
}
