use crate::options::structs::CustomError;
use log::{error, warn};
use std::process::{Command, Output};
use std::sync::Arc;
use tokio::time::Duration;

/// # Fn `get_pid`
/// ## for initializing process of unstoppable grubbing metrics.
/// 
/// *input*      : `&str`
///
/// *output*     : `None` if cant get process PID | `Some(Output)` on success
///
/// *initiator*  : fn `is_frozen`, fn `utils::files::file_handler`, fn `utils::files::service_handler`
///
/// *managing*   : process name
///
/// *depends on* : - 
/// 
pub async fn get_pid(name: &str) -> Option<Output> {
    let name = Arc::new(name.to_string());
    let res =
        // todo : unwrap change - can be unsafe
        tokio::task::spawn_blocking(move || Command::new("pidof").arg(&*name).output()).await.unwrap();
    if let Ok(output) = res {
        if output.stderr.is_empty() && output.stdout.is_empty() {
            None
        } else {
            Some(output)
        }
    } else {
        None
    }
}

/// # Fn `is_active`
/// ## for checking process's activity state
/// 
/// *input*      : `&str`
///
/// *output*     : `true` if process running | `false` if not
///
/// *initiator*  : fn `utils::files::file_handler`, fn `utils::files::service_handler`
///
/// *managing*   : process name
///
/// *depends on* : - 
/// 
pub async fn is_active(name: &str) -> bool {
    let arc_name = Arc::new(name.to_string());
    tokio::task::spawn_blocking(move || {
        let output = Command::new("pidof")
            .arg(&*arc_name)
            .output()
            .unwrap_or_else(|_| {
                error!("Failed to execute command 'pidof'");
                std::process::exit(101);
            });
        !String::from_utf8_lossy(&output.stdout).trim().is_empty()
    })
    .await
    .unwrap()
}

/// # Fn `is_frozen`
/// ## for checking process's hibernation state
/// 
/// *input*      : `&str`
///
/// *output*     : `true` if process is frozen | `false` if not
///
/// *initiator*  : fn `utils::files::file_handler`, fn `utils::files::service_handler`
///
/// *managing*   : process name
///
/// *depends on* : fn `get_pid`
/// 
pub async fn is_frozen(name: &str) -> bool {
    let temp: Output;
    if let Some(output) = get_pid(name).await {
        temp = output;
    } else {
        return false;
    }
    let pid = String::from_utf8_lossy(&temp.stdout);
    let pid = pid.trim();
    let arc_pid = Arc::new(pid.to_string());
    if pid.is_empty() {
        false
    } else {
        tokio::task::spawn_blocking(move || {
            let cmd = Command::new("ps")
                .args(["-o", "stat=", "-p", &arc_pid])
                .output()
                .unwrap_or_else(|_| {
                    error!("Failed to execute ps command");
                    std::process::exit(101);
                });
            String::from_utf8_lossy(&cmd.stdout).contains("T")
        })
        .await
        .unwrap()
    }
}

/// # Fn `terminate_process`
/// ## for stop current process
/// 
/// *input*      : `&str`
///
/// *output*     : ()
///
/// *initiator*  : fn `utils::files::file_handler`, fn `utils::files::service_handler`, fn `utils::run_daemons`
///
/// *managing*   : process name
///
/// *depends on* : -
/// 
pub async fn terminate_process(name: &str) {
    let _ = Command::new("pkill")
        .arg(name)
        .output()
        .unwrap_or_else(|_| {
            error!("Failed to execute command 'pkill'");
            std::process::exit(101);
        });
}

/// # Fn `terminate_process`
/// ## for freeze/hibernate current process
/// 
/// *input*      : `&str`
///
/// *output*     : ()
///
/// *initiator*  : fn `utils::run_daemons`
///
/// *managing*   : process name
///
/// *depends on* : -
/// 
pub async fn freeze_process(name: &str) {
    let _ = Command::new("pkill")
        .args(["-STOP", name])
        .output()
        .unwrap_or_else(|_| {
            error!("Failed to freeze process");
            std::process::exit(101);
        });
}

/// # Fn `unfreeze_process`
/// ## for unfreeze/hibernate current process
/// 
/// *input*      : `&str`
///
/// *output*     : ()
///
/// *initiator*  : fn `utils::run_daemons`
///
/// *managing*   : process name
///
/// *depends on* : -
/// 
pub async fn unfreeze_process(name: &str) {
    let _ = Command::new("pkill")
        .args(["-CONT", name])
        .output()
        .unwrap_or_else(|_| {
            error!("Failed to unfreeze process");
            std::process::exit(101);
        });
}

/// # Fn `restart_process`
/// ## for restarting current process
/// 
/// *input*      : `&str`, &str
///
/// *output*     : ()
///
/// *initiator*  : fn `utils::run_daemons`
///
/// *managing*   : process name and path to its exec file
///
/// *depends on* : fn `start_process`, fn `terminate_process`
/// 
pub async fn restart_process(name: &str, path: &str) -> Result<(), CustomError> {
    terminate_process(name).await;
    tokio::time::sleep(Duration::from_millis(100)).await;
    start_process(name, path).await
}

/// # Fn `start_process`
/// ## for starting current process
/// 
/// *input*      : `&str`, &str
///
/// *output*     : ()
///
/// *initiator*  : fn `restart_process`
///
/// *managing*   : process name and path to its exec file
///
/// *depends on* : -
/// 
pub async fn start_process(name: &str, path: &str) -> Result<(), CustomError> {
    // let runsh = format!("{} {}", "exec", path);
    let mut command = Command::new(path);
    // command.arg(path);

    match command.spawn() {
        Ok(_) => {
            warn!("Process {} is running now!", name);
            Ok(())
        }
        Err(er) => {
            println!("{:?}", er);
            Err(CustomError::Fatal)
        }
    }
}

#[cfg(test)]
mod process_unittests {
    use super::*;
    // 1 full cycle - start -> restart -> stop
    // 2 full cycle - start -> freeze -> unfreze -> stop
    // 2x is active
    // 2x is frozen
    // 2x pidof

    // rewrite, its a pipe
    #[tokio::test]
    async fn full_cycle_with_restart() {
        // let _ = std::io::stdout().write_all(b"");
        let res1 = start_process("restart-prc", "./tests/examples/restart-prc").await;
        assert!(res1.is_ok());
        let res2 =
            restart_process("restart-prc", "./tests/examples/restart-prc").await;
        assert!(res2.is_ok());
        let _ = terminate_process("restart-prc").await;
        let res3 = is_active("restart-prc").await;
        assert!(!res3);
    }
    // rewrite, its a pipe
    #[tokio::test]
    async fn full_cycle_with_freeze() {
        assert!(true);
    }
    #[tokio::test]
    async fn is_active_check() {
        let res1 = start_process("tmp-prc", "./tests/examples/tmp-prc").await;
        assert!(res1.is_ok());
        assert!(is_active("tmp-prc").await);
        let _ = terminate_process("tmp-prc").await;
    }
    #[tokio::test]
    async fn isnt_active_check() {
        assert!(!is_active("invalid-process-name").await);
    }
    #[tokio::test]
    async fn is_frozen_check() {
        let res1 = start_process("freeze-check", "./tests/examples/freeze-check").await;
        assert!(res1.is_ok());
        assert!(!is_frozen("freeze-check").await);
    }
    #[tokio::test]
    async fn pidof_active_process() {
        assert!(get_pid("pidof-prc").await.is_none());
        let res1 = start_process("pidof-prc", "./tests/examples/pidof-prc").await;
        assert!(res1.is_ok());
        assert!(get_pid("pidof-prc").await.is_some());
        let _ = terminate_process("pidof-prc").await;
    }

    // broken mechanism need to check
    #[tokio::test]
    async fn pidof_disabled_process() {
        assert!(get_pid("invalid-process-name").await.is_none());
    }
}
