use std::fs::{create_dir, File};
use std::io::Write;
use std::path::{Path, PathBuf};

use reqwest::{Client, Error};
use structopt::StructOpt;

use mangadex_api::types::MangaId;
use mangadex_api::v2::responses::ResponseType;
use mangadex_api::v2::MangaDexV2;
use mangadex_api::MangaDexClient;

#[derive(StructOpt)]
#[structopt(
    name = "MangaDex Manga Cover Downloader",
    about = "Download the cover image(s) for a manga."
)]
struct Opt {
    /// Numerical manga ID.
    #[structopt()]
    manga_id: MangaId,
    /// Location to save the cover(s).
    #[structopt(parse(from_os_str), default_value = "./")]
    output: PathBuf,
    /// Download all manga covers including previous ones.
    #[structopt(short, long)]
    all: bool,
}

#[tokio::main]
async fn main() {
    let opt = Opt::from_args();

    if let Err(e) = run(opt).await {
        use std::process;
        eprintln!("Application error: {}", e);
        process::exit(1);
    }
}

async fn run(opt: Opt) -> Result<(), Error> {
    let mangadex_client = MangaDexV2::default();

    if !opt.output.is_dir() {
        let _ = create_dir(&opt.output);
    }

    println!("Downloading cover images into \"{}\"", opt.output.display());

    if opt.all {
        let manga_covers = mangadex_client
            .manga_covers(opt.manga_id)
            .send()
            .await?
            .ok()
            .unwrap();

        for cover_data in manga_covers.data() {
            let cover_url = cover_data.url();

            println!("Downloading \"{}\"", parse_file_name_from_url(cover_url));
            download_file(mangadex_client.client(), cover_url, &opt.output).await?;
        }
    } else {
        let manga = mangadex_client
            .manga(opt.manga_id)
            .send()
            .await?
            .ok()
            .unwrap();
        let cover_url = manga.data().main_cover_url();

        println!("Downloading \"{}\"", parse_file_name_from_url(cover_url));
        download_file(mangadex_client.client(), cover_url, &opt.output).await?;
    }

    println!("Done");

    Ok(())
}

/// Parse the filename from the URL.
///
/// This does not have any safety guarantees so if there is an error, it will panic.
fn parse_file_name_from_url<U: Into<String>>(url: U) -> String {
    Path::new(url.into().as_str())
        .file_name()
        .unwrap()
        .to_str()
        .unwrap()
        .split('?')
        .next()
        .unwrap()
        .into()
}

/// Download the URL contents into the local filesystem.
async fn download_file<U: Into<String>>(
    client: &Client,
    url: U,
    output: &PathBuf,
) -> Result<(), Error> {
    let url = url.into();
    let file_name = parse_file_name_from_url(url.clone());

    let bytes = client.get(url.as_str()).send().await?.bytes().await?;
    let mut file_buffer = File::create(output.join(file_name)).unwrap();
    let _ = file_buffer.write_all(&bytes);

    Ok(())
}
