use std::io;
use std::process::Stdio;
use std::time::Duration;

use crate::AppResult;
use crate::api::send_message_or_file;
use crate::cli::{Level, ParseMode};
use crate::config::Config;

pub(crate) async fn run_command_and_notify(
    config: &Config,
    command: &str,
    title: Option<String>,
    level: Option<Level>,
    parse_mode: Option<ParseMode>,
    interval: Option<u64>,
) -> AppResult<()> {
    if let Some(interval) = interval {
        if interval < 1 {
            return Err(io::Error::other("Interval must be at least 1 second").into());
        }

        eprintln!("Running '{command}' every {interval}s (Ctrl+C to stop)");
        loop {
            let message = command_output_message(command).await?;
            if let Err(error) =
                send_message_or_file(config, message, title.clone(), level, parse_mode).await
            {
                eprintln!("Error: {error}");
            }
            tokio::time::sleep(Duration::from_secs(interval)).await;
        }
    }

    let message = command_output_message(command).await?;
    send_message_or_file(config, message, title, level, parse_mode).await
}

async fn command_output_message(command: &str) -> AppResult<String> {
    let output = tokio::process::Command::new(shell_program())
        .arg(shell_arg())
        .arg(command)
        .stdout(Stdio::piped())
        .stderr(Stdio::piped())
        .output()
        .await?;

    let mut message = String::new();
    message.push_str(&String::from_utf8_lossy(&output.stdout));
    message.push_str(&String::from_utf8_lossy(&output.stderr));

    if !output.status.success() {
        let code = output.status.code().map_or_else(
            || "terminated by signal".to_string(),
            |code| code.to_string(),
        );
        message = format!("Exit code: {code}\n{message}");
    }

    Ok(message.trim_end_matches('\n').to_string())
}

#[cfg(windows)]
fn shell_program() -> String {
    "cmd".to_string()
}

#[cfg(windows)]
fn shell_arg() -> &'static str {
    "/C"
}

#[cfg(not(windows))]
fn shell_program() -> String {
    std::env::var("SHELL")
        .ok()
        .filter(|shell| !shell.is_empty())
        .unwrap_or_else(|| "/bin/sh".to_string())
}

#[cfg(not(windows))]
fn shell_arg() -> &'static str {
    "-c"
}
