use crate::io::reader::buffer_line_counter::{BUFF_READER_CAPACITY, ReadResult, read_lines};
use crate::io::reader::{AsyncLineReader, StreamEvent};
use anyhow::{Context, Result, anyhow};
use std::process::Stdio;
use tokio::io::BufReader;
use tokio::process::{Child, ChildStdout, Command};

pub struct CommandReader {
    reader: BufReader<ChildStdout>,
    child: Child,
    ready: bool,
}

impl CommandReader {
    pub async fn new(command: String) -> Result<CommandReader> {
        spawn_command(command).await
    }
}

#[cfg(not(windows))]
#[allow(clippy::unused_async)]
async fn spawn_command(command: String) -> Result<CommandReader> {
    let trap_command = format!("trap '' INT; {command}");

    let mut child = Command::new("sh")
        .arg("-c")
        .arg(trap_command)
        .stdout(Stdio::piped())
        .spawn()
        .context("Could not spawn process")?;

    let stdout = child
        .stdout
        .take()
        .ok_or_else(|| anyhow!("Could not capture stdout of spawned process"))?;

    let reader = BufReader::with_capacity(BUFF_READER_CAPACITY, stdout);

    Ok(CommandReader {
        reader,
        child,
        ready: false,
    })
}

#[cfg(windows)]
async fn spawn_command(_command: String) -> Result<CommandReader> {
    Err(anyhow!("The --exec flag is not supported on Windows"))
}

impl Drop for CommandReader {
    fn drop(&mut self) {
        let _ = self.child.start_kill();
    }
}

impl AsyncLineReader for CommandReader {
    async fn next(&mut self) -> Result<StreamEvent> {
        if !self.ready {
            self.ready = true;

            return Ok(StreamEvent::Started);
        }

        let event = match read_lines(&mut self.reader).await? {
            ReadResult::Eof => {
                let _ = self.child.wait().await;
                StreamEvent::Ended
            }
            ReadResult::Line(line) => StreamEvent::Line(line),
            ReadResult::Batch(lines) => StreamEvent::Lines(lines),
        };

        Ok(event)
    }
}
