use reqwest::{header, Client};

use crate::types::{GroupId, MangaId, TagId};
use crate::v2::builder::{
    GetChapterBuilder, GetFollowTypesBuilder, GetGroupBuilder, GetGroupChaptersBuilder,
    GetMangaBuilder, GetMangaChaptersBuilder, GetMangaCoversBuilder, GetMangaRelationTypesBuilder,
    GetTagBuilder, GetTagsBuilder, GetUserBuilder, GetUserChaptersBuilder,
    GetUserFollowedMangaBuilder, GetUserFollowedUpdatesBuilder, GetUserMangaDataBuilder,
    GetUserMangaRatingsBuilder, GetUserSettingsBuilder, SetUserChaptersReadBuilder,
};
use crate::web_scrape::builder::{
    GetLatestUpdatesScrapeBuilder, GetMostPopularMangaScrapeBuilder, LoginAjaxBuilder,
    SearchMangaScrapeBuilder,
};
use crate::MangaDexClient;

/// An asynchronous client to make requests to MangaDex's v2 API with.
pub struct MangaDexV2 {
    client: Client,
}

impl Default for MangaDexV2 {
    fn default() -> Self {
        Self::new()
    }
}

impl MangaDexClient for MangaDexV2 {
    fn client(&self) -> &Client {
        &self.client
    }

    fn api_url(&self) -> String {
        self.base_url().to_owned() + "/api/v2"
    }
}

impl MangaDexV2 {
    /// Construct a new `MangaDexV2` client.
    pub fn new() -> Self {
        Self::builder().build()
    }

    /// Create a `MangaDexV2Builder` to configure a `MangaDexV2` client.
    pub fn builder() -> MangaDexV2Builder {
        MangaDexV2Builder::new()
    }

    /// Create a new login builder instance for logging into MangaDex.
    ///
    /// # Parameters
    ///
    /// - `username`: MangaDex username.
    /// - `password`: Plain-text MangaDex password.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #     use mangadex_api::web_scrape::responses::LoginAjaxResponseType;
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let login_resp = mangadex_client
    ///     .login_ajax("my_username", "hunter2")
    ///     .send()
    ///     .await?;
    ///
    /// assert_eq!(login_resp, LoginAjaxResponseType::IncorrectUsernameOrPassword);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn login_ajax<T>(&self, username: T, password: T) -> LoginAjaxBuilder
    where
        T: Into<String>,
    {
        LoginAjaxBuilder::new(self, username, password)
    }

    /// Get detailed information about a chapter.
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::GetChapterError;
    /// #
    /// # async fn try_main() -> Result<(), GetChapterError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #     use mangadex_api::MangaDexClient;
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let chapter = mangadex_client
    ///     .chapter()
    ///     .chapter_id(1)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*chapter.data().id(), 1);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn chapter(&self) -> GetChapterBuilder {
        GetChapterBuilder::new(&self)
    }

    /// Get the list of follow types a user can set for a manga.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let follow_types = mangadex_client
    ///     .follow_types()
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert!(!follow_types.data().is_empty());
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn follow_types(&self) -> GetFollowTypesBuilder {
        GetFollowTypesBuilder::new(&self)
    }

    /// Get detailed information about a group.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical group ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let group = mangadex_client
    ///     .group(1)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*group.data().id(), 1);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn group(&self, id: GroupId) -> GetGroupBuilder {
        GetGroupBuilder::new(&self, id)
    }

    /// Get partial information about the chapters belonging to a group.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical group ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// // `page()` and `limit()` are optional.
    /// let group_chapters = mangadex_client
    ///     .group_chapters(1)
    ///     .page(2)
    ///     .limit(10)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*group_chapters.code(), 200);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn group_chapters(&self, id: MangaId) -> GetGroupChaptersBuilder {
        GetGroupChaptersBuilder::new(&self, id)
    }

    /// Fetch the manga IDs that have recent updates by scraping the `/updates/{page}` page.
    ///
    /// By default, 50 results are returned.
    ///
    /// This only scrapes the IDs and does not fetch the full manga information due to the volume
    /// of manga per page. Making API calls to fetch the manga information for each of them is a
    /// huge burden on the MangaDex servers.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let manga_updates = mangadex_client
    ///     .latest_updates_scrape()
    ///     .page(1)
    ///     .send()
    ///     .await?;
    ///
    /// assert_eq!(manga_updates.len(), 50);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn latest_updates_scrape(&self) -> GetLatestUpdatesScrapeBuilder {
        GetLatestUpdatesScrapeBuilder::new(self)
    }

    /// Get detailed information about a manga.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical manga ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let manga = mangadex_client
    ///     .manga(1)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*manga.data().id(), 1);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn manga(&self, id: MangaId) -> GetMangaBuilder {
        GetMangaBuilder::new(&self, id)
    }

    /// Get partial information about the chapters belonging to a manga.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical manga ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// // `page()` and `limit()` are optional.
    /// let manga_chapters = mangadex_client
    ///     .manga_chapters(1)
    ///     .page(2)
    ///     .limit(10)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*manga_chapters.code(), 200);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn manga_chapters(&self, id: MangaId) -> GetMangaChaptersBuilder {
        GetMangaChaptersBuilder::new(&self, id)
    }

    /// Get a list of covers belonging to a manga.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical manga ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let manga_covers = mangadex_client
    ///     .manga_covers(1)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert!(!manga_covers.data().is_empty());
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn manga_covers(&self, id: MangaId) -> GetMangaCoversBuilder {
        GetMangaCoversBuilder::new(&self, id)
    }

    /// Get all manga relation types.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let manga_relations_types = mangadex_client
    ///     .manga_relation_types()
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert!(!manga_relations_types.data().is_empty());
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn manga_relation_types(&self) -> GetMangaRelationTypesBuilder {
        GetMangaRelationTypesBuilder::new(&self)
    }

    /// Fetch the manga IDs that have recent updates by scraping the `/title/7/{page}` page.
    ///
    /// By default, 50 results are returned.
    ///
    /// This only scrapes the IDs and does not fetch the full manga information due to the volume
    /// of manga per page. Making API calls to fetch the manga information for each of them is a
    /// huge burden on the MangaDex servers.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let popular_manga = mangadex_client
    ///     .most_popular_scrape()
    ///     .page(1)
    ///     .send()
    ///     .await?;
    ///
    /// assert_eq!(popular_manga.len(), 50);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn most_popular_scrape(&self) -> GetMostPopularMangaScrapeBuilder {
        GetMostPopularMangaScrapeBuilder::new(self)
    }

    /// Search MangaDex for manga titles.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let manga_ids = mangadex_client
    ///     .search_scrape()
    ///     .title("witch")
    ///     .send()
    ///     .await?;
    ///
    /// // Searching requires logging in first so there will be no results.
    /// assert!(manga_ids.is_empty());
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn search_scrape(&self) -> SearchMangaScrapeBuilder {
        SearchMangaScrapeBuilder::new(self)
    }

    /// Get detailed information about a tag.
    ///
    /// # Parameters
    ///
    /// - `id`: The numerical tag ID.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let tag = mangadex_client
    ///     .tag(1)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*tag.data().id(), 1);
    /// assert_eq!(tag.data().name(), "4-Koma");
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn tag(&self, id: TagId) -> GetTagBuilder {
        GetTagBuilder::new(&self, id)
    }

    /// Get all manga tags.
    ///
    /// # Examples
    ///
    /// ```
    /// # async fn try_main() -> Result<(), reqwest::Error> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let tags = mangadex_client
    ///     .tags()
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert!(!tags.data().is_empty());
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn tags(&self) -> GetTagsBuilder {
        GetTagsBuilder::new(&self)
    }

    /// Get detailed information about a user.
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user = mangadex_client
    ///     .user()
    ///     .user_id(2)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user.data().id(), 2);
    /// assert_eq!(user.data().username(), "Holo");
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user(&self) -> GetUserBuilder {
        GetUserBuilder::new(&self)
    }

    /// Get partial information about the chapters uploaded by the user.
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_chapters = mangadex_client
    ///     .user_chapters()
    ///     .user_id(2)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_chapters.code(), 200);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user_chapters(&self) -> GetUserChaptersBuilder {
        GetUserChaptersBuilder::new(&self)
    }

    /// 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.
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_chapters = mangadex_client
    ///     .user_followed_manga()
    ///     .user_id(2) // User ID 2 is currently public so no authentication is needed.
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_chapters.code(), 200);
    /// #
    /// # Ok(())
    /// # }
    /// ```
    pub fn user_followed_manga(&self) -> GetUserFollowedMangaBuilder {
        GetUserFollowedMangaBuilder::new(&self)
    }

    /// Get the latest uploaded chapters for the manga that the user has followed, as well as basic
    /// related manga information.
    ///
    /// **Note**: This requires authentication (i.e. logging in).
    ///
    /// Ordered by timestamp, descending (the datetime when the chapter is available)
    ///
    /// Limit 100 chapters per page. This is not configurable.
    ///
    /// The results are filtered by the authorized user's chapter language filter setting.
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_chapters = mangadex_client
    ///     .user_followed_updates()
    ///     .user_id(2)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// // You must be logged in and can only access your own updates.
    /// assert_eq!(*user_chapters.code(), 403);
    /// #
    /// # Ok(())
    /// # }
    /// ```
    pub fn user_followed_updates(&self) -> GetUserFollowedUpdatesBuilder {
        GetUserFollowedUpdatesBuilder::new(&self)
    }

    /// Get a user's personal data for a manga.
    ///
    /// **Note**: This requires authentication (i.e. logging in).
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_manga_data = mangadex_client
    ///     .user_manga_data(1)
    ///     .user_id(2)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_manga_data.code(), 403);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user_manga_data(&self, manga_id: MangaId) -> GetUserMangaDataBuilder {
        GetUserMangaDataBuilder::new(&self, manga_id)
    }

    /// Set or unset chapter read markers.
    ///
    /// **Note**: This requires authentication (i.e. logging in).
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_chapters_marked_resp = mangadex_client
    ///     .user_chapters_marker()
    ///     .user_id(2)
    ///     .chapters(vec![1, 2, 3])
    ///     .mark_read(true)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_chapters_marked_resp.code(), 403);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user_chapters_marker(&self) -> SetUserChaptersReadBuilder {
        SetUserChaptersReadBuilder::new(&self)
    }

    /// Get a user's manga ratings.
    ///
    /// **Note**: This requires authentication (i.e. logging in).
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_manga_ratings = mangadex_client
    ///     .user_manga_ratings()
    ///     .user_id(2)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_manga_ratings.code(), 403);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user_manga_ratings(&self) -> GetUserMangaRatingsBuilder {
        GetUserMangaRatingsBuilder::new(&self)
    }

    /// Get a user's settings on MangaDex.
    ///
    /// **Note**: This requires authentication (i.e. logging in).
    ///
    /// # Examples
    ///
    /// ```
    /// # use mangadex_api::v2::builder::UserRequestError;
    /// #
    /// # async fn try_main() -> Result<(), UserRequestError> {
    /// #     use mangadex_api::v2::{responses::ResponseType, MangaDexV2};
    /// #
    /// let mangadex_client = MangaDexV2::default();
    ///
    /// let user_settings = mangadex_client
    ///     .user_settings()
    ///     .me(true)
    ///     .send()
    ///     .await?
    ///     .ok()
    ///     .unwrap();
    ///
    /// assert_eq!(*user_settings.code(), 403);
    /// #
    /// #     Ok(())
    /// # }
    /// ```
    pub fn user_settings(&self) -> GetUserSettingsBuilder {
        GetUserSettingsBuilder::new(&self)
    }
}

/// A builder to configure a `MangaDexV2` client.
#[derive(Default)]
pub struct MangaDexV2Builder {
    user_agent: Option<String>,
}

impl MangaDexV2Builder {
    /// Construct a new `MangaDexV2Builder`.
    pub fn new() -> MangaDexV2Builder {
        MangaDexV2Builder {
            ..Default::default()
        }
    }

    /// Set a custom user agent for the client.
    pub fn user_agent<T: Into<String>>(&mut self, user_agent: T) -> &mut Self {
        self.user_agent = Some(user_agent.into());
        self
    }

    /// Build the client.
    pub fn build(&self) -> MangaDexV2 {
        let mut headers = header::HeaderMap::new();
        headers.insert(
            header::HOST,
            header::HeaderValue::from_static("mangadex.org"),
        );
        headers.insert(
            header::REFERER,
            header::HeaderValue::from_static("https://mangadex.org"),
        );
        headers.insert(header::DNT, header::HeaderValue::from_static("1"));
        headers.insert(
            header::CONNECTION,
            header::HeaderValue::from_static("Keep-Alive"),
        );
        if let Some(user_agent) = &self.user_agent {
            // If the user agent can't be added, gracefully handle it by excluding it from the sent
            // headers.
            if let Ok(user_agent_header) = header::HeaderValue::from_str(user_agent.as_str()) {
                headers.insert(header::USER_AGENT, user_agent_header);
            }
        }

        let client = Client::builder()
            .default_headers(headers)
            // Store cookies for functionality such as saving login session.
            .cookie_store(true)
            .build()
            .unwrap();

        MangaDexV2 { client }
    }
}
