#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64")
    )
))]
use keystone_engine::{Arch, Keystone, KeystoneError, Mode};

#[cfg(all(
    feature = "patch_asm",
    target_arch = "x86_64",
    any(target_os = "linux", target_os = "macos")
))]
use keystone_engine::{Arch, Keystone, KeystoneError, Mode, OptionType, OptionValue};

use crate::error::SigHookError;

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "aarch64")
    )
))]
const PATCH_ASM_WIDTH: usize = 4;

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "aarch64")
    )
))]
pub(crate) fn assemble_patch_opcode(address: u64, asm: &str) -> Result<u32, SigHookError> {
    let bytes = assemble_bytes(address, asm)?;
    to_u32_opcode(bytes)
}

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "x86_64")
    )
))]
pub(crate) fn assemble_bytes(address: u64, asm: &str) -> Result<Vec<u8>, SigHookError> {
    let trimmed = asm.trim();
    if trimmed.is_empty() {
        return Err(SigHookError::AsmEmptyInput);
    }

    #[cfg(all(target_arch = "x86_64", any(target_os = "linux", target_os = "macos")))]
    {
        let bytes = assemble_x86_64(trimmed, address)?;
        return Ok(bytes);
    }

    #[cfg(any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "aarch64")
    ))]
    {
        let bytes = assemble_aarch64(trimmed, address)?;
        return Ok(bytes);
    }

    #[allow(unreachable_code)]
    Err(SigHookError::UnsupportedPlatform)
}

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "aarch64")
    )
))]
fn assemble_aarch64(asm: &str, address: u64) -> Result<Vec<u8>, SigHookError> {
    let engine = keystone_engine_init(Arch::ARM64, Mode::LITTLE_ENDIAN)?;
    let output = engine
        .asm(asm.to_string(), address)
        .map_err(map_keystone_error)?;
    Ok(output.bytes)
}

#[cfg(all(
    feature = "patch_asm",
    target_arch = "x86_64",
    any(target_os = "linux", target_os = "macos")
))]
fn assemble_x86_64(asm: &str, address: u64) -> Result<Vec<u8>, SigHookError> {
    let engine = keystone_engine_init(Arch::X86, Mode::MODE_64)?;
    engine
        .option(OptionType::SYNTAX, OptionValue::SYNTAX_ATT)
        .map_err(map_keystone_error)?;

    let output = engine
        .asm(asm.to_string(), address)
        .map_err(map_keystone_error)?;
    Ok(output.bytes)
}

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "x86_64")
    )
))]
fn keystone_engine_init(arch: Arch, mode: Mode) -> Result<Keystone, SigHookError> {
    Keystone::new(arch, mode).map_err(map_keystone_error)
}

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "macos", target_arch = "x86_64"),
        all(target_os = "linux", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "x86_64")
    )
))]
fn map_keystone_error(_err: KeystoneError) -> SigHookError {
    SigHookError::AsmAssembleFailed
}

#[cfg(all(
    feature = "patch_asm",
    any(
        all(target_os = "macos", target_arch = "aarch64"),
        all(target_os = "linux", target_arch = "aarch64")
    )
))]
fn to_u32_opcode(bytes: Vec<u8>) -> Result<u32, SigHookError> {
    if bytes.len() != PATCH_ASM_WIDTH {
        return Err(SigHookError::AsmSizeMismatch {
            expected: PATCH_ASM_WIDTH,
            actual: bytes.len(),
        });
    }

    let mut opcode = [0u8; PATCH_ASM_WIDTH];
    opcode.copy_from_slice(&bytes);
    Ok(u32::from_le_bytes(opcode))
}
