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

use crate::types::{GroupId, Language, MangaDexResponseStatus, StatusCode};
use crate::v2::responses::partials::{GroupPartial, PartialChapterData, UserPartial};
use crate::v2::responses::{ErrorResponse, ResponseType};

#[derive(Debug, Deserialize, Clone, PartialOrd, PartialEq)]
#[serde(untagged)]
pub enum GroupResponseType {
    Group(GroupResponse),
    GroupIncludeChapters(GroupIncludeChaptersResponse),
    Error(ErrorResponse),
}

impl ResponseType for GroupResponseType {
    type OkOutput = GroupResponse;

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

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

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

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

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

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

/// This struct should not be used directly.
#[derive(Deserialize, Debug, PartialOrd, PartialEq, Clone)]
#[serde(rename_all = "camelCase")]
pub struct GroupData {
    #[serde(flatten)]
    group_partial: GroupPartial,
    /// Comma-separated alternative names.
    ///
    /// Note: Every subsequent name has a space before it.
    /// Example: "Doki, Doki Scans"
    alt_names: String,
    language: Language,
    leader: UserPartial,
    /// List of users that belong to the group.
    members: Vec<UserPartial>,
    description: String,
    website: String,
    /// Discord invite URL.
    discord: String,
    /// IRC server URL.
    irc_server: String,
    /// IRC channel within the specified IRC server.
    irc_channel: String,
    /// Contact email address.
    email: String,
    /// Year-month-day (YYYY-MM-DD) of when the group was created on MangaDex.
    #[cfg(feature = "chrono")]
    founded: NaiveDate,
    /// Year-month-day (YYYY-MM-DD) of when the group was created on MangaDex.
    #[cfg(not(feature = "chrono"))]
    founded: String,
    /// Number of likes the group has received in total.
    likes: u64,
    follows: u64,
    /// Number of views the group's uploaded chapters have received.
    views: u64,
    /// Number of chapters uploaded.
    chapters: u32,
    /// Forum thread ID dedicated to the group.
    thread_id: u64,
    /// Number of posts made in the group's forum thread.
    thread_posts: u64,
    /// Whether an admin has restricted the group.
    is_locked: bool,
    /// Whether the group has been disabled.
    is_inactive: bool,
    /// Unknown purpose.
    ///
    /// Presumably, the default amount of time (in seconds?) from when the group uploads a chapter
    /// to when it becomes public.
    delay: u64,
    /// The last time when a change to the group was made.
    #[cfg(feature = "chrono")]
    #[serde(with = "ts_seconds")]
    last_updated: DateTime<Utc>,
    #[cfg(not(feature = "chrono"))]
    last_updated: u64,
    /// MangaDex URL to the group banner image.
    ///
    /// The filename is the group ID.
    banner: Option<String>,
}

impl GroupData {
    pub fn id(&self) -> &GroupId {
        &self.group_partial.id()
    }

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

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

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

    pub fn leader(&self) -> &UserPartial {
        &self.leader
    }

    pub fn members(&self) -> &Vec<UserPartial> {
        &self.members
    }

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

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

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

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

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

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

    #[cfg(feature = "chrono")]
    pub fn founded(&self) -> &NaiveDate {
        &self.founded
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

impl GroupIncludeChaptersData {
    pub fn group(&self) -> &GroupData {
        &self.group
    }

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

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