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

use crate::types::{MangaDexAtHome, MangaDexResponseStatus, StatusCode, UserId, UserLevel};
use crate::v2::responses::partials::{ChapterPartial, GroupPartial};
use crate::v2::responses::{ErrorResponse, ResponseType};

#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(untagged)]
pub enum UserResponseType {
    User(UserResponse),
    UserIncludeChapters(UserIncludeChaptersResponse),
    Error(ErrorResponse),
}

impl ResponseType for UserResponseType {
    type OkOutput = UserResponse;

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

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

/// Information about the user.
///
/// This is the response from the `/api/v2/user/{id}`.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct UserResponse {
    code: StatusCode,
    status: MangaDexResponseStatus,
    data: UserData,
}

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

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

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

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UserData {
    id: UserId,
    username: String,
    /// User privilege level.
    level_id: UserLevel,
    /// Timestamp when the user joined MangaDex.
    #[cfg(feature = "chrono")]
    #[serde(with = "ts_seconds")]
    joined: DateTime<Utc>,
    #[cfg(not(feature = "chrono"))]
    joined: u64,
    /// Timestamp when the user last logged in.
    #[cfg(feature = "chrono")]
    #[serde(with = "ts_seconds")]
    last_seen: DateTime<Utc>,
    #[cfg(not(feature = "chrono"))]
    last_seen: u64,
    website: String,
    /// Self-written description about the user.
    biography: String,
    /// Number of views the user's uploaded chapters have received.
    views: u64,
    /// Number of chapters uploaded by the user.
    uploads: u32,
    premium: bool,
    /// Flag designating whether the user is using the MangaDex@Home program?
    md_at_home: MangaDexAtHome,
    /// MangaDex URL for the user's avatar image.
    avatar: Option<String>,
}

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

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

    pub fn level_id(&self) -> &UserLevel {
        &self.level_id
    }

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

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

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

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

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

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

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

    pub fn uploads(&self) -> &u32 {
        &self.uploads
    }

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

    pub fn md_at_home(&self) -> &MangaDexAtHome {
        &self.md_at_home
    }

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

/// Information about the user with chapters uploaded by the user.
///
/// This is the response from the `/api/v2/user/{id}?include=chapters`.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
pub struct UserIncludeChaptersResponse {
    code: StatusCode,
    status: MangaDexResponseStatus,
    data: UserIncludeChaptersData,
}

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

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

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

/// This struct should not be used directly.
#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct UserIncludeChaptersData {
    user: UserData,
    /// Partial information about the chapters the user has uploaded.
    chapters: Vec<ChapterPartial>,
    /// Other groups that have worked with this user for some, or all, uploaded chapters.
    groups: Vec<GroupPartial>,
}

impl UserIncludeChaptersData {
    pub fn user(&self) -> &UserData {
        &self.user
    }

    pub fn chapters(&self) -> &Vec<ChapterPartial> {
        &self.chapters
    }

    pub fn groups(&self) -> &Vec<GroupPartial> {
        &self.groups
    }
}
