use reqwest::Error;

use crate::types::{ChapterId, LocationServer};
use crate::v2::responses::ChapterResponseType;
use crate::v2::MangaDexV2;
use crate::MangaDexClient;

#[derive(Debug)]
pub enum GetChapterError {
    IDNotProvidedError,
    RequestError(Error),
    ParseError(Error),
}

impl From<Error> for GetChapterError {
    fn from(error: Error) -> Self {
        if error.is_body() {
            Self::ParseError(error)
        } else {
            Self::RequestError(error)
        }
    }
}

/// Builder for the `/api/v2/chapter/{id|hash}` endpoint.
pub struct GetChapterBuilder<'a> {
    md_client: &'a MangaDexV2,
    id: Option<ChapterId>,
    hash: Option<String>,
    server: Option<LocationServer>,
    saver: Option<bool>,
    mark_read: Option<bool>,
}

impl<'a> GetChapterBuilder<'a> {
    pub fn new(md_client: &'a MangaDexV2) -> Self {
        Self {
            md_client,
            id: None,
            hash: None,
            server: None,
            saver: None,
            mark_read: None,
        }
    }

    /// Set the numeric chapter ID.
    ///
    /// This takes priority over the hash if both are set.
    pub fn chapter_id(&mut self, id: ChapterId) -> &mut Self {
        self.id = Some(id);
        self
    }

    /// Set the unique chapter hash ID.
    ///
    /// If this and the numeric ID are set, the numeric ID takes priority.
    pub fn hash<T: Into<String>>(&mut self, hash: T) -> &mut Self {
        self.hash = Some(hash.into());
        self
    }

    /// Set this to override location-based server assignment.
    pub fn server(&mut self, server: LocationServer) -> &mut Self {
        self.server = Some(server);
        self
    }

    /// Use low-quality images to save bandwidth.
    ///
    /// Default: false
    pub fn saver(&mut self, saver: bool) -> &mut Self {
        self.saver = Some(saver);
        self
    }

    /// Mark the chapter as read.
    ///
    /// Default: true
    pub fn mark_read(&mut self, mark_read: bool) -> &mut Self {
        self.mark_read = Some(mark_read);
        self
    }

    /// Send the request to MangaDex with the inputted parameters.
    pub async fn send(&self) -> Result<ChapterResponseType, GetChapterError> {
        let id = if let Some(id) = self.id {
            id.to_string()
        } else if let Some(hash) = &self.hash {
            hash.to_owned()
        } else {
            return Err(GetChapterError::IDNotProvidedError);
        };

        let mut params = Vec::new();
        if let Some(server) = &self.server {
            if server != &LocationServer::Worldwide {
                // TODO: Make `into()` work for `LocationServer`.
                params.push(("server", server.mangadex_repr()));
            }
        }
        if let Some(saver) = self.saver {
            params.push(("saver", if saver { "1" } else { "0" }));
        }
        if let Some(mark_read) = self.mark_read {
            params.push(("mark_read", if mark_read { "1" } else { "0" }));
        }

        Ok(self
            .md_client
            .client()
            .get(&format!(
                "{base_url}/chapter/{chapter_id}",
                base_url = self.md_client.api_url(),
                chapter_id = id
            ))
            .query(&params)
            .send()
            .await?
            .json::<ChapterResponseType>()
            .await?)
    }
}
