use std::path::PathBuf;

use anyhow::Result;
use clap::Parser;
use tokio::{
    fs::File,
    io::{AsyncReadExt, stdin as tokiostdin},
};

use crate::{
    bd_system::BdSystem, doctor::DefaultDoctor, host::Hosts, is_a_terminal::IsATerminal,
    media::StdOutput,
};

#[derive(Parser, Debug)]
#[command(version, about)]
pub struct BdocCli {
    #[arg(
        short,
        long,
        help = "Path to JSON hosts file (reads from stdin if omitted)"
    )]
    file_name: Option<PathBuf>,

    #[arg(
        short,
        long,
        default_value_t = 10,
        help = "Timeout in seconds for each request represented as an integer"
    )]
    timeout: u64,

    #[arg(short, long, default_value_t = 10, help = "Limit concurrent tests")]
    concurrent_tests: usize,
}

impl Default for BdocCli {
    fn default() -> Self {
        Self::parse()
    }
}

impl BdocCli {
    pub fn new(file_name: Option<PathBuf>, timeout: u64, concurrent_tests: usize) -> Self {
        Self {
            file_name,
            timeout,
            concurrent_tests,
        }
    }

    pub async fn run(&self, stdin: impl IsATerminal) -> Result<()> {
        BdSystem::new(
            DefaultDoctor::new(self.timeout),
            Hosts::from(self.hosts_data(stdin).await?.as_str())?,
            StdOutput::new(),
            self.concurrent_tests,
        )
        .run()
        .await;

        Ok(())
    }

    async fn hosts_data(&self, stdin: impl IsATerminal) -> Result<String> {
        let mut content = String::new();
        match &self.file_name {
            Some(path) => self.read_file(path, &mut content).await?,
            None => self.read_input(stdin, &mut content).await?,
        }
        Ok(content)
    }

    async fn read_file(&self, from_path: &PathBuf, to_content: &mut String) -> Result<()> {
        let _ = File::open(from_path)
            .await?
            .read_to_string(to_content)
            .await;
        Ok(())
    }

    async fn read_input(&self, stdin: impl IsATerminal, to_content: &mut String) -> Result<()> {
        if !stdin.is_a_terminal() {
            let _ = tokiostdin().read_to_string(to_content).await;
        } else {
            anyhow::bail!(
                "no input file specified and stdin is a terminal; use -f <FILE> or pipe input"
            );
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use crate::{cli::BdocCli, is_a_terminal::FakeStdin};

    #[tokio::test]
    async fn test_error_on_stdin_terminal() {
        let cli = BdocCli::new(None, 10, 10);

        assert!(cli.run(FakeStdin::new(true)).await.is_err())
    }
}
