use libc::c_char;
use std::ffi::c_void;
use std::fs;
use std::sync::atomic::AtomicUsize;
use tracing::{debug, error};

use crate::error::PrehookError;
use crate::{HOOK_REGISTRY, interpose};
pub static MAIN_EXE_BASE: AtomicUsize = AtomicUsize::new(0);

pub type LibcStartMain = extern "C" fn(
    main: extern "C" fn(i32, *mut *mut c_char, *mut *mut c_char) -> i32,
    argc: i32,
    ubp_av: *mut *mut c_char,
    init: extern "C" fn(),
    fini: extern "C" fn(),
    rtld_fini: extern "C" fn(),
    stack_end: *mut c_void,
) -> i32;

pub fn get_base_addr() -> Result<usize, PrehookError> {
    let maps = fs::read_to_string("/proc/self/maps")
        .map_err(|e| PrehookError::TargetNotFound(e.to_string()))?;

    for line in maps.lines() {
        if line.contains("r-xp") && !line.contains("[") {
            let addr_str = line.split('-').next().unwrap();
            let addr = usize::from_str_radix(addr_str, 16)
                .map_err(|e| PrehookError::TargetNotFound(e.to_string()))?;
            return Ok(addr);
        }
    }

    Err(PrehookError::TargetNotFound(
        "Could not find executable segment".to_string(),
    ))
}

#[interpose]
fn __libc_start_main(
    main: extern "C" fn(i32, *mut *mut c_char, *mut *mut c_char) -> i32,
    argc: i32,
    ubp_av: *mut *mut c_char,
    init: extern "C" fn(),
    fini: extern "C" fn(),
    rtld_fini: extern "C" fn(),
    stack_end: *mut c_void,
) -> i32 {
    debug!(target: "prehook", "__libc_start_main intercepted");

    let base = match get_base_addr() {
        Ok(addr) => addr,
        Err(e) => {
            error!(target: "prehook", "Failed to get base address: {}", e);
            return call_original!(main, argc, ubp_av, init, fini, rtld_fini, stack_end);
        }
    };

    // store the main hook point binary base address
    MAIN_EXE_BASE.store(base, std::sync::atomic::Ordering::SeqCst);

    debug!(target: "prehook", "Base address: 0x{:x}", base);

    HOOK_REGISTRY.apply_all(base);

    call_original!(main, argc, ubp_av, init, fini, rtld_fini, stack_end)
}
