use super::Ephemeral;
use crate::{ApAttachment, ApCollection, ApContext, ApDateTime, ApEndpoint, ApImage, ApTag};
use anyhow::{self, Result};
use jdt_maybe_multiple::MaybeMultiple;
use jdt_maybe_reference::MaybeReference;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_with::serde_as;
use std::fmt::{Debug, Display, Formatter};
use wasm_bindgen::{
    convert::{FromWasmAbi, IntoWasmAbi},
    prelude::*,
};

lazy_static! {
    pub static ref PUBLIC_COLLECTION: Vec<String> = {
        vec![
            "https://www.w3.org/ns/activitystreams#Public".to_string(),
            "as:Public".to_string(),
            "Public".to_string(),
        ]
    };
}

#[derive(Serialize, Clone, PartialEq, Eq, Default, Hash, Ord, PartialOrd)]
#[serde(untagged)]
pub enum ApAddress {
    Address(String),
    #[default]
    None,
}

impl Debug for ApAddress {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            ApAddress::Address(addr) => write!(f, "{}", addr),
            ApAddress::None => write!(f, "none"),
        }
    }
}

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

impl wasm_bindgen::describe::WasmDescribe for ApAddress {
    fn describe() {
        JsValue::describe()
    }
}

impl wasm_bindgen::convert::IntoWasmAbi for ApAddress {
    type Abi = <JsValue as IntoWasmAbi>::Abi;

    fn into_abi(self) -> Self::Abi {
        serde_wasm_bindgen::to_value(&self).unwrap().into_abi()
    }
}

impl wasm_bindgen::convert::FromWasmAbi for ApAddress {
    type Abi = <JsValue as FromWasmAbi>::Abi;

    unsafe fn from_abi(js: Self::Abi) -> Self {
        serde_wasm_bindgen::from_value(JsValue::from_abi(js)).unwrap()
    }
}

impl From<ApAddress> for JsValue {
    fn from(val: ApAddress) -> Self {
        serde_wasm_bindgen::to_value(&val).unwrap()
    }
}

impl TryFrom<JsValue> for ApAddress {
    type Error = serde_wasm_bindgen::Error;

    fn try_from(value: JsValue) -> Result<Self, Self::Error> {
        serde_wasm_bindgen::from_value(value)
    }
}

impl ApAddress {
    pub fn is_public(&self) -> bool {
        if let ApAddress::Address(x) = self {
            x.to_lowercase() == *"https://www.w3.org/ns/activitystreams#public"
        } else {
            false
        }
    }

    pub fn get_public() -> Self {
        ApAddress::Address("https://www.w3.org/ns/activitystreams#Public".to_string())
    }
}

impl From<String> for ApAddress {
    fn from(address: String) -> Self {
        ApAddress::Address(address)
    }
}

impl<'de> Deserialize<'de> for ApAddress {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        let value = Option::<String>::deserialize(deserializer)?;
        match value {
            Some(s) if s == "null" => Ok(ApAddress::None),
            Some(s) => Ok(ApAddress::Address(s)),
            None => Ok(ApAddress::None),
        }
    }
}

impl TryFrom<Value> for ApAddress {
    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, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub struct ApPublicKey {
    pub id: String,
    pub owner: String,
    pub public_key_pem: String,
}

impl TryFrom<Value> for ApPublicKey {
    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, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub struct ApCapabilities {
    // Pleroma Capabilities
    #[serde(skip_serializing_if = "Option::is_none")]
    pub accepts_chat_messages: Option<bool>,

    // Pixelfed Capabilities
    #[serde(skip_serializing_if = "Option::is_none")]
    pub accounce: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub like: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reply: Option<String>,

    // Enigmatick Capabilities
    #[serde(skip_serializing_if = "Option::is_none")]
    pub enigmatick_encryption: Option<bool>,
}

impl TryFrom<Value> for ApCapabilities {
    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, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub struct ApGenerator {
    #[serde(rename = "type")]
    pub kind: ApGeneratorType,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub implements: MaybeMultiple<ApImplements>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ApGeneratorType {
    #[serde(alias = "application")]
    Application,
    #[default]
    Unknown,
}

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

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct ApImplements {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub href: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "camelCase")]
pub struct ApAssertionMethod {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    #[serde(rename = "type")]
    pub kind: ApAssertionMethodType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub controller: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub public_key_multibase: Option<String>,
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum ApAssertionMethodType {
    #[serde(alias = "multikey")]
    Multikey,
    #[default]
    Unknown,
}

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

#[derive(Serialize, PartialEq, Eq, Deserialize, Clone, Debug, Default, Hash, Ord, PartialOrd)]
pub enum ApActorType {
    #[serde(alias = "application")]
    Application,
    #[serde(alias = "group")]
    Group,
    #[serde(alias = "organization")]
    Organization,
    #[serde(alias = "person")]
    Person,
    #[serde(alias = "service")]
    Service,
    #[serde(alias = "tombstone")]
    Tombstone,
    #[default]
    #[serde(alias = "unknown")]
    Unknown,
}

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

#[serde_as]
#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApActor {
    #[serde(rename = "@context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<ApContext>,

    #[serde(rename = "type")]
    pub kind: ApActorType,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<ApAddress>,

    pub preferred_username: String,
    pub inbox: String,
    pub outbox: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub followers: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub following: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub subscribers: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub liked: Option<String>,

    pub public_key: ApPublicKey,

    #[serde(skip_serializing_if = "MaybeReference::is_none")]
    #[serde(default)]
    pub featured: MaybeReference<ApCollection>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub featured_tags: Option<String>,

    // BlueSky seems to use an array here (or the bridges do)
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub url: MaybeMultiple<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub manually_approves_followers: Option<bool>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub published: Option<ApDateTime>,

    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub tag: MaybeMultiple<ApTag>,

    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub attachment: MaybeMultiple<ApAttachment>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub endpoints: Option<ApEndpoint>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub icon: Option<ApImage>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub image: Option<ApImage>,

    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub also_known_as: MaybeMultiple<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub discoverable: Option<bool>,

    // perhaps SoapBox/Pleroma-specific
    #[serde(skip_serializing_if = "Option::is_none")]
    pub capabilities: Option<ApCapabilities>,

    // Enigmatick-specific
    #[serde(skip_serializing_if = "Option::is_none")]
    pub keys: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub ephemeral: Option<Ephemeral>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub updated: Option<ApDateTime>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub generator: Option<ApGenerator>,

    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub assertion_method: MaybeMultiple<ApAssertionMethod>,
}

impl Debug for ApActor {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Actor[{}{}{}{}{}{}]",
            self.preferred_username,
            self.name
                .as_ref()
                .map(|n| format!(", name: {}", n))
                .unwrap_or_default(),
            self.id
                .as_ref()
                .map(|i| format!(", id: {}", i))
                .unwrap_or_default(),
            self.summary
                .as_ref()
                .map(|s| format!(
                    ", summary: {:.60}{}",
                    s,
                    if s.len() > 60 { "..." } else { "" }
                ))
                .unwrap_or_default(),
            if !self.also_known_as.as_ref().is_empty() {
                format!(", aka: {}", self.also_known_as)
            } else {
                String::new()
            },
            if self.manually_approves_followers.unwrap_or(false) {
                ", locked"
            } else {
                ""
            }
        )
    }
}

impl Display for ApActor {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        write!(f, "{}", self.id.clone().unwrap_or(ApAddress::None))
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApActorTerse {
    pub id: String,
    pub url: MaybeMultiple<String>,
    pub name: Option<String>,
    pub preferred_username: String,
    pub tag: MaybeMultiple<ApTag>,
    pub icon: Option<ApImage>,
    pub webfinger: Option<String>,
}

impl Default for ApActor {
    fn default() -> ApActor {
        ApActor {
            context: Some(ApContext::Plain(
                "https://www.w3.org/ns/activitystreams".to_string(),
            )),
            kind: ApActorType::default(),
            name: None,
            summary: None,
            id: None,
            preferred_username: String::new(),
            inbox: String::new(),
            outbox: String::new(),
            followers: None,
            following: None,
            subscribers: None,
            liked: None,
            public_key: ApPublicKey::default(),
            featured: MaybeReference::None,
            featured_tags: None,
            url: MaybeMultiple::None,
            manually_approves_followers: None,
            published: None,
            tag: MaybeMultiple::None,
            attachment: MaybeMultiple::None,
            endpoints: None,
            icon: None,
            image: None,
            also_known_as: MaybeMultiple::None,
            discoverable: None,
            capabilities: None,
            keys: None,
            ephemeral: None,
            updated: None,
            generator: None,
            assertion_method: MaybeMultiple::None,
        }
    }
}

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

impl From<ApActor> for ApActorTerse {
    fn from(actor: ApActor) -> Self {
        ApActorTerse {
            id: actor.id.unwrap_or_default().to_string(),
            url: actor.url,
            name: actor.name,
            preferred_username: actor.preferred_username,
            tag: actor.tag,
            icon: actor.icon,
            webfinger: actor.ephemeral.and_then(|x| x.webfinger),
        }
    }
}
