use serde::{Deserialize, Deserializer};

use crate::types::{
    HentaiMode, Language, LatestUpdatesSetting, MangaDexResponseStatus, StatusCode, Tag, TagId,
    UserId,
};
use crate::v2::responses::{ErrorResponse, ResponseBody, ResponseType};

#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(untagged)]
pub enum UserSettingsResponseType {
    Ok(UserSettingsResponse),
    Err(ErrorResponse),
}

impl ResponseType for UserSettingsResponseType {
    type OkOutput = UserSettingsResponse;

    /// Check if the endpoint response is ok.
    fn is_ok(&self) -> bool {
        matches!(self, Self::Ok(_))
    }

    /// Get the data from the endpoint if the response is ok.
    fn ok(self) -> Option<Self::OkOutput> {
        match self {
            Self::Ok(data) => Some(data),
            _ => None,
        }
    }
}

/// Response from the `/api/v2/user/{id}/settings` endpoint.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct UserSettingsResponse {
    code: StatusCode,
    status: MangaDexResponseStatus,
    data: UserSettingsData,
}

impl UserSettingsResponse {
    pub fn code(&self) -> &StatusCode {
        &self.code
    }

    pub fn status(&self) -> &MangaDexResponseStatus {
        &self.status
    }

    pub fn data(&self) -> &UserSettingsData {
        &self.data
    }
}

impl ResponseBody for UserSettingsResponse {}

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UserSettingsData {
    id: UserId,
    hentai_mode: HentaiMode,
    latest_updates: LatestUpdatesSetting,
    show_moderated_posts: bool,
    show_unavailable_chapters: bool,
    #[serde(
        rename = "shownChapterLangs",
        deserialize_with = "parse_chapter_language"
    )]
    shown_chapter_languages: Vec<Language>,
    #[serde(deserialize_with = "parse_tags")]
    excluded_tags: Vec<Tag>,
}

impl UserSettingsData {
    pub fn id(&self) -> &UserId {
        &self.id
    }

    pub fn hentai_mode(&self) -> &HentaiMode {
        &self.hentai_mode
    }

    pub fn latest_updates(&self) -> &LatestUpdatesSetting {
        &self.latest_updates
    }

    pub fn show_moderated_posts(&self) -> &bool {
        &self.show_moderated_posts
    }

    pub fn shown_chapter_languages(&self) -> &Vec<Language> {
        &self.shown_chapter_languages
    }

    pub fn excluded_tags(&self) -> &Vec<Tag> {
        &self.excluded_tags
    }
}

#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
struct ShownChapterLang {
    id: String,
}

#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
struct ExcludedTag {
    id: TagId,
}

/// Custom deserializer for the `shown_chapter_languages` field.
///
/// This returns the parsed `Language` variants.
fn parse_chapter_language<'de, D>(d: D) -> Result<Vec<Language>, D::Error>
where
    D: Deserializer<'de>,
{
    Deserialize::deserialize(d).map(|languages: Vec<ShownChapterLang>| {
        languages
            .into_iter()
            .map(|shown_chapter_language| {
                Language::from(shown_chapter_language.id.parse::<u8>().unwrap_or(0))
            })
            .collect::<Vec<Language>>()
    })
}

/// Custom deserializer for the `excluded_tags` field.
///
/// This returns the parsed `Tag` variants.
fn parse_tags<'de, D>(d: D) -> Result<Vec<Tag>, D::Error>
where
    D: Deserializer<'de>,
{
    Deserialize::deserialize(d).map(|tags: Vec<ExcludedTag>| {
        tags.into_iter()
            .map(|excluded_tag| Tag::from(excluded_tag.id))
            .collect::<Vec<Tag>>()
    })
}
