use clap::Parser;
use reqwest::{Client, Url};
use tokio::{
    fs::File,
    io::{AsyncWriteExt, BufWriter},
    task::JoinSet,
};

/// A simple example of a command line application that fetches a URL.
#[derive(Parser, Debug)]
struct Args {
    #[clap(required = true)]
    urls: Vec<Url>,
}

/// This is the main entry point for the application.
///
/// It is equivalent to the following code:
///
/// ```no_run
/// use std::future::Future;
///
/// use tokio::runtime::Runtime;
///
/// fn main() {
///     Runtime::new()
///         .unwrap()
///         .block_on(async { /* Body code here */ });
/// }
/// ```
#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let args = Args::parse();
    let client = Client::builder().build()?;
    let mut v = JoinSet::new();
    for url in args.urls {
        let filename = match url.path().rsplit_terminator('/').next() {
            Some("") | None => "index.html",
            Some(name) => name,
        }
        .to_string();

        let request = client.get(url);
        v.spawn(async move {
            let mut res = request.send().await?.error_for_status()?;

            let mut file = BufWriter::new(File::create_new(filename).await?);

            while let Some(chunk) = res.chunk().await? {
                file.write_all(&chunk).await?;
            }

            file.shutdown().await?;
            anyhow::Ok(())
        });
    }
    let mut errs: Vec<String> = Vec::new();
    while let Some(res) = v.join_next().await {
        match res {
            Ok(Err(e)) => errs.push(e.to_string()),
            Err(e) => errs.push(e.to_string()),
            _ => {}
        }
    }

    if !errs.is_empty() {
        anyhow::bail!("Some errors occurred:\n{}", errs.join("\n"));
    }

    Ok(())
}