use crate::OrdValue;
use crate::{
    ApActor, ApCollection, ApImage, ApInstrument, ApLink, ApNote, ApQuestion, ApSession,
    ApTombstone,
};
use anyhow::Result;
use chrono::{DateTime, Utc};
use jdt_maybe_multiple::MaybeMultiple;
use jdt_maybe_reference::Identifier;
use jdt_maybe_reference::Referenceable;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::fmt::{Debug, Display, Formatter};

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord)]
#[serde(untagged)]
pub enum ApContext {
    Plain(String),
    Complex(OrdValue),
}

impl Debug for ApContext {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApContext::Plain(ctx) => write!(f, "{}", ctx),
            ApContext::Complex(ord_val) => write!(f, "{:?}", ord_val),
        }
    }
}

impl Display for ApContext {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

impl Default for ApContext {
    fn default() -> Self {
        ApContext::full()
    }
}

impl ApContext {
    pub fn activity_streams() -> Self {
        ApContext::Plain("https://www.w3.org/ns/activitystreams".to_string())
    }

    pub fn full() -> Self {
        let context = json!([
            "https://www.w3.org/ns/activitystreams",
            "https://w3id.org/security/v1",
            {
                "manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
                "alsoKnownAs": {
                    "@id": "as:alsoKnownAs",
                    "@type": "@id"
                },
                "keys": {
                    "@id": "as:keys",
                    "@type": "@id"
                },
                "discoverable": "as:discoverable",
                "conversation": "as:conversation",
                "capabilities": "as:capabilities",
                "acceptsChatMessages": "as:acceptsChatMessages",
                "enigmatickEncryption": "as:enigmatickEncryption"
            }
        ]);
        ApContext::Complex(OrdValue(context))
    }
}

impl TryFrom<Value> for ApContext {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self> {
        serde_json::from_value(value).map_err(anyhow::Error::msg)
    }
}

#[derive(Serialize, Deserialize, Clone)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ApTimelineObject {
    Note(ApNote),
    Question(ApQuestion),
}

impl Debug for ApTimelineObject {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApTimelineObject::Note(note) => write!(f, "{:?}", note),
            ApTimelineObject::Question(question) => write!(f, "{:?}", question),
        }
    }
}

impl Display for ApTimelineObject {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApTimelineObject::Note(note) => write!(f, "{}", note),
            ApTimelineObject::Question(question) => write!(f, "{}", question),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum ApObject {
    Tombstone(ApTombstone),
    Session(ApSession),
    Instrument(ApInstrument),
    Note(ApNote),
    Question(ApQuestion),
    Actor(ApActor),
    Collection(ApCollection),

    // These members exist to catch unknown object types
    Identifier(Identifier),
}

impl Debug for ApObject {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApObject::Tombstone(tombstone) => write!(f, "{:?}", tombstone),
            ApObject::Session(session) => write!(f, "{:?}", session),
            ApObject::Instrument(instrument) => write!(f, "{:?}", instrument),
            ApObject::Note(note) => write!(f, "{:?}", note),
            ApObject::Question(question) => write!(f, "{:?}", question),
            ApObject::Actor(actor) => write!(f, "{:?}", actor),
            ApObject::Collection(collection) => write!(f, "{:?}", collection),
            ApObject::Identifier(id) => write!(f, "{:?}", id),
        }
    }
}

impl Display for ApObject {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApObject::Tombstone(tombstone) => write!(f, "{}", tombstone),
            ApObject::Session(session) => write!(f, "{}", session),
            ApObject::Instrument(instrument) => write!(f, "{}", instrument),
            ApObject::Note(note) => write!(f, "{}", note),
            ApObject::Question(question) => write!(f, "{}", question),
            ApObject::Actor(actor) => write!(f, "{}", actor),
            ApObject::Collection(collection) => write!(f, "{}", collection),
            ApObject::Identifier(id) => write!(f, "{}", id),
        }
    }
}

impl From<ApObject> for MaybeMultiple<ApObject> {
    fn from(data: ApObject) -> Self {
        MaybeMultiple::Single(data)
    }
}

impl From<ApInstrument> for ApObject {
    fn from(instrument: ApInstrument) -> Self {
        ApObject::Instrument(instrument)
    }
}

impl From<ApCollection> for ApObject {
    fn from(collection: ApCollection) -> Self {
        ApObject::Collection(collection)
    }
}

impl Referenceable for ApObject {}

impl FromIterator<ApInstrument> for Vec<ApObject> {
    fn from_iter<I: IntoIterator<Item = ApInstrument>>(iter: I) -> Self {
        iter.into_iter().map(ApObject::from).collect()
    }
}

impl ApObject {
    pub fn timestamp(&self) -> DateTime<Utc> {
        match self {
            ApObject::Note(note) => {
                if let Some(ephemeral) = note.ephemeral.clone() {
                    ephemeral.updated_at.unwrap_or(Utc::now())
                } else {
                    Utc::now()
                }
            }
            ApObject::Question(question) => {
                if let Some(ephemeral) = question.ephemeral.clone() {
                    ephemeral.updated_at.unwrap_or(Utc::now())
                } else {
                    Utc::now()
                }
            }
            _ => Utc::now(),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq, Ord, PartialOrd)]
pub enum ApMentionType {
    Mention,
}

impl Display for ApMentionType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum ApHashtagType {
    Hashtag,
}

impl Display for ApHashtagType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, Ord, PartialOrd)]
pub enum ApEmojiType {
    Emoji,
}

impl Display for ApEmojiType {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct ApMention {
    #[serde(rename = "type")]
    pub kind: ApMentionType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub href: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub value: Option<String>,
}

impl Debug for ApMention {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Mention[{}{}{}]",
            self.name.as_deref().unwrap_or("unnamed"),
            self.href
                .as_ref()
                .map(|h| format!("@{}", h))
                .unwrap_or_default(),
            self.value
                .as_ref()
                .map(|v| format!("={}", v))
                .unwrap_or_default()
        )
    }
}

impl Display for ApMention {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Mention[{}]",
            self.href
                .as_ref()
                .map(|h| format!("@{}", h))
                .unwrap_or_default(),
        )
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct ApHashtag {
    #[serde(rename = "type")]
    pub kind: ApHashtagType,
    pub name: String,
    pub href: String,
}

impl Debug for ApHashtag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.name)
    }
}

impl Display for ApHashtag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

impl From<ApNote> for Vec<ApHashtag> {
    fn from(note: ApNote) -> Self {
        note.tag
            .multiple()
            .iter()
            .filter_map(|tag| {
                if let ApTag::Hashtag(tag) = tag {
                    Some(tag.clone())
                } else {
                    None
                }
            })
            .collect()
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd)]
pub struct ApEmoji {
    #[serde(rename = "type")]
    pub kind: ApEmojiType,
    pub id: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated: Option<String>,
    pub icon: ApImage,
}

impl Debug for ApEmoji {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, ":{}: ({})", self.name, self.id)
    }
}

impl Display for ApEmoji {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(untagged)]
pub enum ApTag {
    Emoji(ApEmoji),
    Mention(ApMention),
    Hashtag(ApHashtag),
    Link(ApLink),
}

impl Debug for ApTag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApTag::Emoji(emoji) => write!(f, "{}", emoji),
            ApTag::Mention(mention) => write!(f, "{}", mention),
            ApTag::Hashtag(hashtag) => write!(f, "{}", hashtag),
            ApTag::Link(link) => write!(f, "{}", link),
        }
    }
}

impl Display for ApTag {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApEndpoint {
    pub shared_inbox: String,
}

impl Debug for ApEndpoint {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "inbox:{}", self.shared_inbox)
    }
}

impl Display for ApEndpoint {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

impl TryFrom<Value> for ApEndpoint {
    type Error = anyhow::Error;

    fn try_from(value: Value) -> Result<Self> {
        serde_json::from_value(value).map_err(anyhow::Error::msg)
    }
}
