#[cfg(feature = "chrono")]
use chrono::{serde::ts_seconds, DateTime, Utc};
use serde::Deserialize;

use crate::types::{
    Demographic, Language, MangaDexResponseStatus, MangaId, MangaLinks, PublicationStatus,
    RelatedMangaType, StatusCode, Tag,
};
use crate::v2::responses::{ErrorResponse, ResponseBody, ResponseType};

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

impl ResponseType for MangaResponseType {
    type OkOutput = MangaResponse;

    /// 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/manga/{id}` endpoint.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct MangaResponse {
    code: StatusCode,
    status: MangaDexResponseStatus,
    data: MangaData,
}

impl ResponseBody for MangaResponse {}

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

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

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

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct MangaData {
    id: MangaId,
    title: String,
    alt_titles: Vec<String>,
    description: String,
    #[serde(rename = "artist")]
    artists: Vec<String>,
    #[serde(rename = "author")]
    authors: Vec<String>,
    publication: Publication,
    tags: Vec<Tag>,
    last_chapter: Option<String>,
    last_volume: Option<String>,
    is_hentai: bool,
    links: Option<MangaLinks>,
    relations: Option<Vec<RelatedManga>>,
    rating: Rating,
    views: u64,
    follows: u64,
    comments: u64,
    #[cfg(feature = "chrono")]
    #[serde(with = "ts_seconds")]
    last_uploaded: DateTime<Utc>,
    #[cfg(not(feature = "chrono"))]
    last_uploaded: u64,
    #[serde(rename = "mainCover")]
    main_cover_url: String,
}

impl MangaData {
    pub fn id(&self) -> &MangaId {
        &self.id
    }

    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn alt_titles(&self) -> &Vec<String> {
        &self.alt_titles
    }

    pub fn description(&self) -> &String {
        &self.description
    }

    pub fn artists(&self) -> &Vec<String> {
        &self.artists
    }

    pub fn authors(&self) -> &Vec<String> {
        &self.authors
    }

    pub fn publication(&self) -> &Publication {
        &self.publication
    }

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

    pub fn last_chapter(&self) -> &Option<String> {
        &self.last_chapter
    }

    pub fn last_volume(&self) -> &Option<String> {
        &self.last_volume
    }

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

    pub fn links(&self) -> &Option<MangaLinks> {
        &self.links
    }

    pub fn relations(&self) -> &Option<Vec<RelatedManga>> {
        &self.relations
    }

    pub fn rating(&self) -> &Rating {
        &self.rating
    }

    pub fn views(&self) -> &u64 {
        &self.views
    }

    pub fn follows(&self) -> &u64 {
        &self.follows
    }

    pub fn comments(&self) -> &u64 {
        &self.comments
    }

    #[cfg(feature = "chrono")]
    pub fn last_uploaded(&self) -> &DateTime<Utc> {
        &self.last_uploaded
    }

    #[cfg(not(feature = "chrono"))]
    pub fn last_uploaded(&self) -> &u64 {
        &self.last_uploaded
    }

    pub fn main_cover_url(&self) -> &String {
        &self.main_cover_url
    }
}

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct Publication {
    /// Two-letter country code.
    ///
    /// Example: "jp".
    language: Language,
    status: PublicationStatus,
    demographic: Demographic,
}

impl Publication {
    pub fn language(&self) -> &Language {
        &self.language
    }

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

    pub fn demographic(&self) -> &Demographic {
        &self.demographic
    }
}

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct RelatedManga {
    id: MangaId,
    title: String,
    #[serde(rename = "type")]
    related_type: RelatedMangaType,
    is_hentai: bool,
}

impl RelatedManga {
    pub fn id(&self) -> &MangaId {
        &self.id
    }

    pub fn title(&self) -> &String {
        &self.title
    }

    pub fn related_type(&self) -> &RelatedMangaType {
        &self.related_type
    }

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

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct Rating {
    bayesian: f64,
    mean: f64,
    /// Number of users that have rated the manga.
    users: u64,
}

impl Rating {
    pub fn bayesian(&self) -> &f64 {
        &self.bayesian
    }

    pub fn mean(&self) -> &f64 {
        &self.mean
    }

    pub fn users(&self) -> &u64 {
        &self.users
    }
}
