#[macro_use]
extern crate lazy_static;

use libc::{c_char};

use std::ffi::{CStr, CString};
use std::sync::{Mutex, Arc};
use std::cell::RefCell;
use std::ptr;
use std::panic;

use silentdragonxlitelib::{commands, lightclient::{LightClient, LightClientConfig}};

/// Helper to create a CString, replacing null bytes to avoid panics
fn safe_cstring(s: &str) -> CString {
    let cleaned: String = s.replace('\0', "");
    CString::new(cleaned).unwrap_or_else(|_| CString::new("Error: failed to create CString").unwrap())
}

/// Helper to create an error CString
fn error_cstring(msg: &str) -> *mut c_char {
    safe_cstring(&format!("Error: {}", msg)).into_raw()
}

// We'll use a MUTEX to store a global lightclient instance, 
// so we don't have to keep creating it. We need to store it here, in rust
// because we can't return such a complex structure back to C++
lazy_static! {
    static ref LIGHTCLIENT: Mutex<RefCell<Option<Arc<LightClient>>>> = Mutex::new(RefCell::new(None));
}

// Check if there is an existing wallet
#[no_mangle]
pub extern fn litelib_wallet_exists(chain_name: *const c_char) -> bool {
    let chain_name_str = unsafe {
        assert!(!chain_name.is_null());

        CStr::from_ptr(chain_name).to_string_lossy().into_owned()
    };

    let config = LightClientConfig::create_unconnected(chain_name_str, None);

    println!("Wallet exists: {}", config.wallet_exists());
    config.wallet_exists()
}

//////hash blake3

#[no_mangle]
pub extern fn blake3_PW(pw: *const c_char) -> *mut c_char{

    let passwd = unsafe {
        assert!(!pw.is_null());

        CStr::from_ptr(pw).to_string_lossy().into_owned()
    };

    let data = passwd.as_bytes();
    // Hash an input all at once.
    let hash1 = blake3::hash(data).to_hex();
    // This is sensitive metadata, do not log it to stdout
    //println!("\nBlake3 Hash: {}", hash1);
    println!("\nBlake3 Hash calculated");

    //let sttring = CString::new(hash1).unwrap();
    let e_str = CString::new(format!("{}", hash1)).unwrap();
    return e_str.into_raw();
}

/// Create a new wallet and return the seed for the newly created wallet.
#[no_mangle]
pub extern fn litelib_initialize_new(dangerous: bool,server: *const c_char) -> *mut c_char {
    let server_str = unsafe {
        assert!(!server.is_null());

        CStr::from_ptr(server).to_string_lossy().into_owned()
    };

    let server = LightClientConfig::get_server_or_default(Some(server_str));
    let (config, latest_block_height) = match LightClientConfig::create(server, dangerous) {
        Ok((c, h)) => (c, h),
        Err(e) => {
            let e_str = CString::new(format!("Error: {}", e)).unwrap();
            return e_str.into_raw();
        }
    };

    let lightclient = match LightClient::new(&config, latest_block_height) {
        Ok(l) => l,
        Err(e) => {
            let e_str = CString::new(format!("Error: {}", e)).unwrap();
            return e_str.into_raw();
        }
    };

    // Initialize logging
    let _ = lightclient.init_logging();

    let seed = match lightclient.do_seed_phrase() {
        Ok(s) => s.dump(),
        Err(e) => {
            let e_str = CString::new(format!("Error: {}", e)).unwrap();
            return e_str.into_raw();
        }
    };

    let lc = Arc::new(lightclient);
    match LightClient::start_mempool_monitor(lc.clone()) {
        Ok(_) => {println!("Starting Mempool")},
        Err(e) => {
            println!("Couldnt start mempool {}", e)
        }
    }
    
    match LIGHTCLIENT.lock() {
        Ok(l) => { l.replace(Some(lc)); },
        Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
    };

    // Return the wallet's seed
    let s_str = safe_cstring(&seed);
    return s_str.into_raw();
}

/// Restore a wallet from the seed phrase
#[no_mangle]
pub extern "C" fn litelib_initialize_new_from_phrase(dangerous: bool, server: *const c_char, 
            seed: *const c_char, birthday: u64, number: u64, overwrite: bool) -> *mut c_char {
    if server.is_null() || seed.is_null() {
        println!("Server or seed is null");
        return ptr::null_mut();
    }

    let server_str = unsafe {
        CStr::from_ptr(server).to_string_lossy().into_owned()
    };
    let seed_str = unsafe {
        CStr::from_ptr(seed).to_string_lossy().into_owned()
    };

    //println!("Initializing with server: {}, seed: {}", server_str, seed_str);

    // Shut down the existing client if one is running, to stop background threads
    if overwrite {
        let old_lc = match LIGHTCLIENT.lock() {
            Ok(l) => l.borrow().clone(),
            Err(poisoned) => poisoned.into_inner().borrow().clone(),
        };
        if let Some(lc) = old_lc {
            lc.shutdown();
        }
    }

    let server = LightClientConfig::get_server_or_default(Some(server_str));
    let (config, _latest_block_height) = match LightClientConfig::create(server, dangerous) {
        Ok((c, h)) => {
            println!("Config created successfully");
            (c, h)
        },
        Err(e) => {
            println!("Error creating config: {}", e);
            let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
            return e_str.into_raw();
        }
    };

    let lightclient = match LightClient::new_from_phrase(seed_str, &config, birthday, number, overwrite) {
        Ok(l) => {
            println!("LightClient created successfully");
            l
        },
        Err(e) => {
            println!("Error creating LightClient: {}", e);
            let e_str = CString::new(format!("Error: {}", e)).unwrap_or_else(|_| CString::new("Error creating CString").unwrap());
            return e_str.into_raw();
        }
    };

    // Initialize logging
    let _ = lightclient.init_logging();

    let lc = Arc::new(lightclient);
    match LightClient::start_mempool_monitor(lc.clone()) {
        Ok(_) => println!("Starting Mempool"),
        Err(e) => println!("Could not start mempool: {}", e)
    }

    match LIGHTCLIENT.lock() {
        Ok(l) => { l.replace(Some(lc)); },
        Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
    };

    let c_str = safe_cstring("OK");
    return c_str.into_raw();
}

// Initialize a new lightclient and store its value
#[no_mangle]
pub extern fn litelib_initialize_existing(dangerous: bool, server: *const c_char) -> *mut c_char {
    let server_str = unsafe {
        assert!(!server.is_null());

        CStr::from_ptr(server).to_string_lossy().into_owned()
    };

    let server = LightClientConfig::get_server_or_default(Some(server_str));
    let (config, _latest_block_height) = match LightClientConfig::create(server,dangerous) {
        Ok((c, h)) => (c, h),
        Err(e) => {
            let e_str = CString::new(format!("Error: {}", e)).unwrap();
            return e_str.into_raw();
        }
    };

    let lightclient = match LightClient::read_from_disk(&config) {
        Ok(l) => l,
        Err(e) => {
            let e_str = CString::new(format!("Error: {}", e)).unwrap();
            return e_str.into_raw();
        }
    };

    // Initialize logging
    let _ = lightclient.init_logging();
    
    let lc = Arc::new(lightclient);
    match LightClient::start_mempool_monitor(lc.clone()) {
        Ok(_) => {println!("Starting Mempool")},
        Err(e) => {
            println!("Couldnt start mempool {}",e)
        }
    }

    match LIGHTCLIENT.lock() {
        Ok(l) => { l.replace(Some(lc)); },
        Err(poisoned) => { poisoned.into_inner().replace(Some(lc)); },
    };

    let c_str = safe_cstring("OK");
    return c_str.into_raw();
}

#[no_mangle]
pub extern fn litelib_execute(cmd: *const c_char, args: *const c_char) -> *mut c_char {
    let result = panic::catch_unwind(|| {
        let cmd_str = unsafe {
            assert!(!cmd.is_null());
            CStr::from_ptr(cmd).to_string_lossy().into_owned()
        };

        let arg_str = unsafe {
            assert!(!args.is_null());
            CStr::from_ptr(args).to_string_lossy().into_owned()
        };

        let resp: String;
        {
            let lightclient: Arc<LightClient>;
            {
                let lc = match LIGHTCLIENT.lock() {
                    Ok(l) => l,
                    Err(poisoned) => poisoned.into_inner(),
                };

                if lc.borrow().is_none() {
                    return error_cstring("Light Client is not initialized");
                }

                lightclient = lc.borrow().as_ref().unwrap().clone();
            };

            let args = if arg_str.is_empty() { vec![] } else { vec![arg_str.as_ref()] };

            resp = commands::do_user_command(&cmd_str, &args, lightclient.as_ref()).clone();
        };

        safe_cstring(&resp).into_raw()
    });

    match result {
        Ok(ptr) => ptr,
        Err(_) => error_cstring("Rust panic in litelib_execute"),
    }
}

// Check is Server Connection is fine
#[no_mangle]
pub extern "C" fn litelib_check_server_online(server: *const c_char) -> bool {
    let server_str = unsafe {
        assert!(!server.is_null());

        CStr::from_ptr(server).to_string_lossy().into_owned()
    };

    let server = LightClientConfig::get_server_or_default(Some(server_str));
    let result = LightClientConfig::create(server, false);

    match result {
        Ok(_) => true,
        Err(_) => false,
    }
}

/// Cleanly shut down the light client, stopping mempool monitor threads.
/// Must be called before exit to prevent hangs.
#[no_mangle]
pub extern "C" fn litelib_shutdown() {
    let lc_option = match LIGHTCLIENT.lock() {
        Ok(l) => l.borrow().clone(),
        Err(poisoned) => poisoned.into_inner().borrow().clone(),
    };

    if let Some(lc) = lc_option {
        lc.shutdown();
    }
}

/**
 * Callers that receive string return values from other functions should call this to return the string 
 * back to rust, so it can be freed. Failure to call this function will result in a memory leak
 */ 
#[no_mangle]
pub extern fn litelib_rust_free_string(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s)
    };
}
