use serde::{Deserialize, Serialize};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};
use super::communities_permissions::CommunityPermission;

#[derive(Clone, Serialize, Deserialize)]
pub struct Community {
    pub id: usize,
    pub created: usize,
    pub title: String,
    pub context: CommunityContext,
    /// The ID of the owner of the community.
    pub owner: usize,
    /// Who can read the community.
    pub read_access: CommunityReadAccess,
    /// Who can write to the community (create posts belonging to it).
    ///
    /// The owner of the community (and moderators) are the ***only*** people
    /// capable of removing posts.
    pub write_access: CommunityWriteAccess,
    /// Who can join the community.
    pub join_access: CommunityJoinAccess,
    // likes
    pub likes: isize,
    pub dislikes: isize,
    // counts
    pub member_count: usize,
}

impl Community {
    /// Create a new [`Community`].
    pub fn new(title: String, owner: usize) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp() as usize,
            title: title.clone(),
            context: CommunityContext {
                display_name: title,
                ..Default::default()
            },
            owner,
            read_access: CommunityReadAccess::default(),
            write_access: CommunityWriteAccess::default(),
            join_access: CommunityJoinAccess::default(),
            likes: 0,
            dislikes: 0,
            member_count: 0,
        }
    }

    /// Create the "void" community. This is where all posts with a deleted community
    /// resolve to.
    pub fn void() -> Self {
        Self {
            id: 0,
            created: 0,
            title: "void".to_string(),
            context: CommunityContext::default(),
            owner: 0,
            read_access: CommunityReadAccess::Joined,
            write_access: CommunityWriteAccess::Owner,
            join_access: CommunityJoinAccess::Nobody,
            likes: 0,
            dislikes: 0,
            member_count: 0,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize, Default)]
pub struct CommunityContext {
    #[serde(default)]
    pub display_name: String,
    #[serde(default)]
    pub description: String,
    #[serde(default)]
    pub is_nsfw: bool,
    #[serde(default)]
    pub enable_questions: bool,
    /// If posts are allowed to set a `title` field.
    #[serde(default)]
    pub enable_titles: bool,
    /// If posts are required to set a `title` field.
    ///
    /// `enable_titles` is required for this setting to work.
    #[serde(default)]
    pub require_titles: bool,
    /// The community's layout in the UI.
    #[serde(default)]
    pub layout: CommunityLayout,
}

/// Who can read a [`Community`].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum CommunityReadAccess {
    /// Everybody can view the community.
    Everybody,
    /// Only people in the community can view the community.
    Joined,
}

impl Default for CommunityReadAccess {
    fn default() -> Self {
        Self::Everybody
    }
}

/// Who can write to a [`Community`].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum CommunityWriteAccess {
    /// Everybody.
    Everybody,
    /// Only people who joined the community can write to it.
    ///
    /// Memberships can be managed by the owner of the community.
    Joined,
    /// Only the owner of the community.
    Owner,
}

impl Default for CommunityWriteAccess {
    fn default() -> Self {
        Self::Joined
    }
}

/// Who can join a [`Community`].
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum CommunityJoinAccess {
    /// Joins are closed. Nobody can join the community.
    Nobody,
    /// All authenticated users can join the community.
    Everybody,
    /// People must send a request to join.
    Request,
}

impl Default for CommunityJoinAccess {
    fn default() -> Self {
        Self::Everybody
    }
}

/// The layout of the [`Community`]'s UI.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CommunityLayout {
    /// The classic timeline-like layout.
    Classic,
    /// A GitHub-esque bug tracker layout.
    BugTracker,
}

impl Default for CommunityLayout {
    fn default() -> Self {
        Self::Classic
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommunityMembership {
    pub id: usize,
    pub created: usize,
    pub owner: usize,
    pub community: usize,
    pub role: CommunityPermission,
}

impl CommunityMembership {
    /// Create a new [`CommunityMembership`].
    pub fn new(owner: usize, community: usize, role: CommunityPermission) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp() as usize,
            owner,
            community,
            role,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PostContext {
    #[serde(default = "default_comments_enabled")]
    pub comments_enabled: bool,
    #[serde(default)]
    pub is_pinned: bool,
    #[serde(default)]
    pub is_profile_pinned: bool,
    #[serde(default)]
    pub edited: usize,
    #[serde(default)]
    pub is_nsfw: bool,
    #[serde(default)]
    pub repost: Option<RepostContext>,
    #[serde(default = "default_reposts_enabled")]
    pub reposts_enabled: bool,
    /// The ID of the question this post is answering.
    #[serde(default)]
    pub answering: usize,
    #[serde(default = "default_reactions_enabled")]
    pub reactions_enabled: bool,
    #[serde(default)]
    pub content_warning: String,
    #[serde(default)]
    pub tags: Vec<String>,
}

fn default_comments_enabled() -> bool {
    true
}

fn default_reposts_enabled() -> bool {
    true
}

fn default_reactions_enabled() -> bool {
    true
}

impl Default for PostContext {
    fn default() -> Self {
        Self {
            comments_enabled: default_comments_enabled(),
            reposts_enabled: default_reposts_enabled(),
            is_pinned: false,
            is_profile_pinned: false,
            edited: 0,
            is_nsfw: false,
            repost: None,
            answering: 0,
            reactions_enabled: default_reactions_enabled(),
            content_warning: String::new(),
            tags: Vec::new(),
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RepostContext {
    /// Should be `false` is `reposting` is `Some`.
    ///
    /// Declares the post to be a repost of another post.
    pub is_repost: bool,
    /// Should be `None` if `is_repost` is true.
    ///
    /// Sets the ID of the other post to load.
    pub reposting: Option<usize>,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Post {
    pub id: usize,
    pub created: usize,
    pub content: String,
    /// The ID of the owner of this post.
    pub owner: usize,
    /// The ID of the [`Community`] this post belongs to.
    pub community: usize,
    /// Extra information about the post.
    pub context: PostContext,
    /// The ID of the post this post is a comment on.
    pub replying_to: Option<usize>,
    pub likes: isize,
    pub dislikes: isize,
    pub comment_count: usize,
    /// IDs of all uploads linked to this post.
    pub uploads: Vec<usize>,
    /// If the post was deleted.
    pub is_deleted: bool,
    /// The ID of the poll associated with this post. 0 means no poll is connected.
    pub poll_id: usize,
    /// The title of the post (in communities where titles are enabled).
    pub title: String,
}

impl Post {
    /// Create a new [`Post`].
    pub fn new(
        content: String,
        community: usize,
        replying_to: Option<usize>,
        owner: usize,
        poll_id: usize,
    ) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp() as usize,
            content,
            owner,
            community,
            context: PostContext::default(),
            replying_to,
            likes: 0,
            dislikes: 0,
            comment_count: 0,
            uploads: Vec::new(),
            is_deleted: false,
            poll_id,
            title: String::new(),
        }
    }

    /// Create a new [`Post`] (as a repost of the given `post_id`).
    pub fn repost(content: String, community: usize, owner: usize, post_id: usize) -> Self {
        let mut post = Self::new(content, community, None, owner, 0);

        post.context.repost = Some(RepostContext {
            is_repost: false,
            reposting: Some(post_id),
        });

        post
    }

    /// Make the given post a reposted post.
    pub fn mark_as_repost(&mut self) {
        self.context.repost = Some(RepostContext {
            is_repost: true,
            reposting: None,
        });
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Question {
    pub id: usize,
    pub created: usize,
    pub owner: usize,
    pub receiver: usize,
    pub content: String,
    /// The `is_global` flag allows any (authenticated) user to respond
    /// to the question. Normally, only the `receiver` can do so.
    ///
    /// If `is_global` is true, `receiver` should be 0 (and vice versa).
    pub is_global: bool,
    /// The number of answers the question has. Should never really be changed
    /// unless the question has `is_global` set to true.
    pub answer_count: usize,
    /// The ID of the community this question is asked to. This should only be > 0
    /// if `is_global` is set to true.
    pub community: usize,
    // likes
    #[serde(default)]
    pub likes: isize,
    #[serde(default)]
    pub dislikes: isize,
    // ...
    #[serde(default)]
    pub context: QuestionContext,
    /// The IP of the question creator for IP blocking and identifying anonymous users.
    #[serde(default)]
    pub ip: String,
}

impl Question {
    /// Create a new [`Question`].
    pub fn new(
        owner: usize,
        receiver: usize,
        content: String,
        is_global: bool,
        ip: String,
    ) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp() as usize,
            owner,
            receiver,
            content,
            is_global,
            answer_count: 0,
            community: 0,
            likes: 0,
            dislikes: 0,
            context: QuestionContext::default(),
            ip,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct QuestionContext {
    #[serde(default)]
    pub is_nsfw: bool,
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PostDraft {
    pub id: usize,
    pub created: usize,
    pub content: String,
    pub owner: usize,
}

impl PostDraft {
    /// Create a new [`PostDraft`].
    pub fn new(content: String, owner: usize) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp() as usize,
            content,
            owner,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Poll {
    pub id: usize,
    pub owner: usize,
    pub created: usize,
    /// The number of milliseconds until this poll can no longer receive votes.
    pub expires: usize,
    // options
    pub option_a: String,
    pub option_b: String,
    pub option_c: String,
    pub option_d: String,
    // votes
    pub votes_a: usize,
    pub votes_b: usize,
    pub votes_c: usize,
    pub votes_d: usize,
}

impl Poll {
    /// Create a new [`Poll`].
    pub fn new(
        owner: usize,
        expires: usize,
        option_a: String,
        option_b: String,
        option_c: String,
        option_d: String,
    ) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            owner,
            created: unix_epoch_timestamp() as usize,
            expires,
            // options
            option_a,
            option_b,
            option_c,
            option_d,
            // votes
            votes_a: 0,
            votes_b: 0,
            votes_c: 0,
            votes_d: 0,
        }
    }
}

/// Poll option (selectors) are stored in the database as numbers 0 to 3.
///
/// This enum allows us to convert from these numbers into letters.
#[derive(Copy, Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum PollOption {
    A,
    B,
    C,
    D,
}

impl From<u8> for PollOption {
    fn from(value: u8) -> Self {
        match value {
            0 => Self::A,
            1 => Self::B,
            2 => Self::C,
            3 => Self::D,
            _ => Self::A,
        }
    }
}

impl Into<u8> for PollOption {
    fn into(self) -> u8 {
        match self {
            Self::A => 0,
            Self::B => 1,
            Self::C => 2,
            Self::D => 3,
        }
    }
}

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PollVote {
    pub id: usize,
    pub owner: usize,
    pub created: usize,
    pub poll_id: usize,
    pub vote: PollOption,
}

impl PollVote {
    /// Create a new [`PollVote`].
    pub fn new(owner: usize, poll_id: usize, vote: PollOption) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            owner,
            created: unix_epoch_timestamp() as usize,
            poll_id,
            vote,
        }
    }
}