use reqwest::Error;
use structopt::StructOpt;

use mangadex_api::types::{ChapterId, GroupId, Language, MangaId, TagId, UserId};
use mangadex_api::v2::MangaDexV2;

#[derive(StructOpt)]
#[structopt(
    name = "MangaDex V2 Simple CLI",
    about = "Simple fetcher to get information from the MangaDex API."
)]
struct Opt {
    /// Get detailed information about a chapter belonging to a manga.
    ///
    /// This can be the numerical chapter ID or hash.
    #[structopt(long)]
    chapter: Option<String>,
    /// List all follow types a user can set for a manga.
    #[structopt(long)]
    follow_types: bool,
    /// Get detailed information about a group.
    #[structopt(long)]
    group: Option<GroupId>,
    /// Get partial information about the chapters belonging to a group.
    #[structopt(long)]
    group_chapters: Option<MangaId>,
    /// Set the languages that should be filtered.
    #[structopt(long)]
    languages: Option<Vec<Language>>,
    /// Fetch the manga IDs that have recent updates.
    ///
    /// This only scrapes the IDs and does not fetch the full manga information due to the volume
    /// of manga per page.
    #[structopt(long)]
    latest_updates: bool,
    /// Get detailed information about a manga.
    #[structopt(long)]
    manga: Option<MangaId>,
    /// Get partial information about the chapters belonging to a manga.
    #[structopt(long)]
    manga_chapters: Option<MangaId>,
    /// Get a list of covers belonging to a manga.
    #[structopt(long)]
    manga_covers: Option<MangaId>,
    /// Fetch the manga IDs that ordered by the ranking starting from the most popular.
    ///
    /// This only scrapes the IDs and does not fetch the full manga information due to the volume
    /// of manga per page.
    #[structopt(long)]
    most_popular: bool,
    /// The page of the paginated results.
    #[structopt(long)]
    page: Option<u32>,
    /// The limit of the paginated results.
    #[structopt(long)]
    limit: Option<u32>,
    /// List all manga relation types.
    #[structopt(long)]
    relation_types: bool,
    /// Get detailed information about a tag.
    #[structopt(long)]
    tag: Option<TagId>,
    /// Get all manga tags.
    #[structopt(long)]
    tags: bool,
    /// MangaDex username for logging in.
    #[structopt(short, long)]
    username: Option<String>,
    /// MangaDex password for logging in.
    #[structopt(short, long, requires = "username")]
    password: Option<String>,
    /// Search for the manga title.
    ///
    /// *NOTE*: This requires logging in first.
    #[structopt(long)]
    search_title: Option<String>,
    /// MangaDex 2-factor 6-digit TOTP authentication code.
    #[structopt(short = "2", long, requires = "username")]
    two_factor: Option<String>,
    /// Get detailed information about a user.
    ///
    /// This can be the numerical user ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user: Option<String>,
    /// Get partial information about the chapters uploaded by a user.
    ///
    /// This can be the numerical user ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user_chapters: Option<String>,
    /// Get a user's followed manga and personal data for them.
    ///
    /// The target user's MDList privacy setting is taken into account
    /// when determining authorization.
    ///
    /// This can be the numerical user ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user_followed_manga: Option<String>,
    /// Get the latest uploaded chapters for the manga that the user has followed, as well as basic
    /// related manga information.
    ///
    /// The target user's MDList privacy setting is taken into account
    /// when determining authorization.
    ///
    /// *NOTE*: This requires logging in first.
    ///
    /// This can be the numerical user ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user_followed_updates: Option<String>,
    /// Get a user's personal data for a manga.
    ///
    /// *NOTE*: This requires logging in first.
    ///
    /// The user ID can be the numerical ID or "me" for the current cookie-authenticated user.
    #[structopt(long, number_of_values = 2)]
    // Using a `Vec<String>` here is awkward when I want the second value to be of type `MangaId`.
    user_manga_data: Option<Vec<String>>,
    /// Get a user's manga ratings.
    ///
    /// *NOTE*: This requires logging in first.
    ///
    /// This can be the numerical user ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user_manga_ratings: Option<String>,
    /// Set or unset chapter read markers.
    ///
    /// *NOTE*: This requires logging in first.
    ///
    /// The user ID can be the numerical ID or "me" for the current cookie-authenticated user.
    #[structopt(long, number_of_values = 2)]
    // Using a `Vec<String>` here is awkward when I want the second value to be of type `MangaId`.
    user_chapters_marker: Option<Vec<String>>,
    /// Get a user's settings.
    ///
    /// *NOTE*: This requires logging in first.
    ///
    /// The user ID can be the numerical ID or "me" for the current cookie-authenticated user.
    #[structopt(long)]
    user_settings: Option<String>,
}

#[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 = MangaDexV2::default();

    if let Some(username) = opt.username {
        let password = match opt.password {
            Some(password) => password,
            None => {
                println!("Password not provided! Enter your MangaDex password.");
                let mut password = String::new();
                std::io::stdin()
                    .read_line(&mut password)
                    .expect("Please enter a password");
                password.trim_end_matches('\n').to_string()
            }
        };
        let mut login_ajax_builder = mangadex.login_ajax(username, password);

        if let Some(two_factor_code) = opt.two_factor {
            login_ajax_builder.two_factor(two_factor_code);
        }

        let login_resp = login_ajax_builder.send().await?;
        println!("Login: {:#?}", login_resp);
    }

    if let Some(chapter_id) = opt.chapter {
        let mut chapter_builder = mangadex.chapter();
        match chapter_id.parse::<ChapterId>() {
            Ok(id) => chapter_builder.chapter_id(id),
            Err(_) => chapter_builder.hash(chapter_id),
        };

        let chapter = chapter_builder.send().await.unwrap();
        println!("Chapter: {:#?}", chapter);

        return Ok(());
    }

    if let Some(group_id) = opt.group {
        let group = mangadex.group(group_id).send().await?;
        println!("Group: {:#?}", group);

        return Ok(());
    }

    if opt.follow_types {
        let follow_types = mangadex.follow_types().send().await?;
        println!("Follow Types: {:#?}", follow_types);

        return Ok(());
    }

    if let Some(group_id) = opt.group_chapters {
        let mut group_chapters_builder = mangadex.group_chapters(group_id);
        if let Some(page) = opt.page {
            group_chapters_builder.page(page);
        }
        if let Some(limit) = opt.limit {
            group_chapters_builder.limit(limit);
        }

        let group_chapters = group_chapters_builder.send().await?;
        println!("Group Chapters: {:#?}", group_chapters);

        return Ok(());
    }

    if opt.latest_updates {
        let mut latest_updates_builder = mangadex.latest_updates_scrape();

        if let Some(page) = opt.page {
            latest_updates_builder.page(page);
        }

        if let Some(languages) = opt.languages {
            latest_updates_builder.chapter_languages(languages);
        }

        let latest_updates = latest_updates_builder.send().await?;
        println!("Latest Manga Updates: {:#?}", latest_updates);

        return Ok(());
    }

    if opt.most_popular {
        let mut most_popular_builder = mangadex.most_popular_scrape();

        if let Some(page) = opt.page {
            most_popular_builder.page(page);
        }

        let most_popular_manga = most_popular_builder.send().await?;
        println!("Most Popular Manga: {:#?}", most_popular_manga);

        return Ok(());
    }

    if let Some(manga_id) = opt.manga {
        let manga = mangadex.manga(manga_id).send().await?;
        println!("Manga: {:#?}", manga);

        return Ok(());
    }

    if let Some(manga_id) = opt.manga_chapters {
        let mut manga_chapters_builder = mangadex.manga_chapters(manga_id);
        if let Some(page) = opt.page {
            manga_chapters_builder.page(page);
        }
        if let Some(limit) = opt.limit {
            manga_chapters_builder.limit(limit);
        }

        let manga_chapters = manga_chapters_builder.send().await?;
        println!("Manga Chapters: {:#?}", manga_chapters);

        return Ok(());
    }

    if let Some(manga_id) = opt.manga_covers {
        let manga_covers = mangadex.manga_covers(manga_id).send().await?;
        println!("Covers: {:#?}", manga_covers);

        return Ok(());
    }

    if opt.relation_types {
        let relation_types = mangadex.manga_relation_types().send().await?;
        println!("Relation Types: {:#?}", relation_types);

        return Ok(());
    }

    if let Some(title) = opt.search_title {
        let manga_ids = mangadex.search_scrape().title(title).send().await?;
        println!("Search Manga Results: {:#?}", manga_ids);

        return Ok(());
    }

    if let Some(tag_id) = opt.tag {
        let tag = mangadex.tag(tag_id).send().await?;
        println!("Tag: {:#?}", tag);

        return Ok(());
    }

    if opt.tags {
        let tags = mangadex.tags().send().await?;
        println!("Tags: {:#?}", tags);

        return Ok(());
    }

    if let Some(user_id) = opt.user {
        let mut user_builder = mangadex.user();
        match user_id.parse::<UserId>() {
            Ok(id) => user_builder.user_id(id),
            Err(_) => user_builder.me(true),
        };

        let user = user_builder.send().await.unwrap();
        println!("User: {:#?}", user);

        return Ok(());
    }

    if let Some(user_id) = opt.user_chapters {
        let mut user_chapters_builder = mangadex.user_chapters();

        match user_id.parse::<UserId>() {
            Ok(id) => user_chapters_builder.user_id(id),
            Err(_) => user_chapters_builder.me(true),
        };

        if let Some(page) = opt.page {
            user_chapters_builder.page(page);
        }
        if let Some(limit) = opt.limit {
            user_chapters_builder.limit(limit);
        }

        let user_chapters = user_chapters_builder.send().await.unwrap();
        println!("User Chapters: {:#?}", user_chapters);

        return Ok(());
    }

    if let Some(user_id) = opt.user_followed_manga {
        let mut user_followed_manga_builder = mangadex.user_followed_manga();
        match user_id.parse::<UserId>() {
            Ok(id) => user_followed_manga_builder.user_id(id),
            Err(_) => user_followed_manga_builder.me(true),
        };

        let user_followed_manga = user_followed_manga_builder.send().await.unwrap();
        println!("User Followed Manga: {:#?}", user_followed_manga);

        return Ok(());
    }

    if let Some(user_id) = opt.user_followed_updates {
        let mut user_followed_updates_builder = mangadex.user_followed_updates();
        match user_id.parse::<UserId>() {
            Ok(id) => user_followed_updates_builder.user_id(id),
            Err(_) => user_followed_updates_builder.me(true),
        };

        let user_followed_updates = user_followed_updates_builder.send().await.unwrap();
        println!("User Followed Manga Updates: {:#?}", user_followed_updates);

        return Ok(());
    }

    if let Some(opt_values) = opt.user_manga_data {
        let user_id = &opt_values[0];
        let manga_id = &opt_values[1].parse::<MangaId>().unwrap();

        let mut user_manga_data_builder = mangadex.user_manga_data(*manga_id);
        match user_id.parse::<UserId>() {
            Ok(id) => user_manga_data_builder.user_id(id),
            Err(_) => user_manga_data_builder.me(true),
        };

        let user_manga_data = user_manga_data_builder.send().await.unwrap();
        println!("User Manga Data: {:#?}", user_manga_data);

        return Ok(());
    }

    if let Some(user_id) = opt.user_manga_ratings {
        let mut user_manga_ratings_builder = mangadex.user_manga_ratings();
        match user_id.parse::<UserId>() {
            Ok(id) => user_manga_ratings_builder.user_id(id),
            Err(_) => user_manga_ratings_builder.me(true),
        };

        let user_manga_ratings = user_manga_ratings_builder.send().await.unwrap();
        println!("User Manga Ratings: {:#?}", user_manga_ratings);

        return Ok(());
    }

    if let Some(opt_values) = opt.user_chapters_marker {
        let user_id = &opt_values[0];
        let chapter_id = &opt_values[1].parse::<ChapterId>().unwrap();

        let mut user_chapters_marker_builder = mangadex.user_chapters_marker();
        match user_id.parse::<UserId>() {
            Ok(id) => user_chapters_marker_builder.user_id(id),
            Err(_) => user_chapters_marker_builder.me(true),
        };

        let chapters = vec![*chapter_id];
        user_chapters_marker_builder
            .chapters(chapters)
            .mark_read(true);

        let user_chapters_marker = user_chapters_marker_builder.send().await.unwrap();
        println!("User Chapter Marker: {:#?}", user_chapters_marker);

        return Ok(());
    }

    if let Some(user_id) = opt.user_settings {
        let mut user_settings_builder = mangadex.user_settings();
        match user_id.parse::<UserId>() {
            Ok(id) => user_settings_builder.user_id(id),
            Err(_) => user_settings_builder.me(true),
        };

        let user_settings = user_settings_builder.send().await.unwrap();
        println!("User Settings: {:#?}", user_settings);

        return Ok(());
    }

    Ok(())
}
