use crate::{ApImage, ApVideo};
use jdt_maybe_multiple::MaybeMultiple;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};
use anyhow::Result;

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
#[serde(untagged)]
pub enum ApAttachment {
    Document(ApDocument),
    Image(ApImage),
    PropertyValue(ApPropertyValue),
    Video(ApVideo),
    Link(ApLink),
    VerifiableIdentityStatement(ApVerifiableIdentityStatement),
}

impl TryFrom<ApImage> for ApDocument {
    type Error = anyhow::Error;

    fn try_from(image: ApImage) -> Result<Self, Self::Error> {
        Ok(ApDocument {
            kind: ApDocumentType::Document,
            media_type: image.media_type,
            url: Some(image.url),
            blurhash: image.blurhash,
            width: image.width,
            height: image.height,
            filename: image.filename,
            path: image.path,
        })
    }
}

impl TryFrom<ApVideo> for ApDocument {
    type Error = anyhow::Error;

    fn try_from(video: ApVideo) -> Result<Self, Self::Error> {
        Ok(ApDocument {
            kind: ApDocumentType::Document,
            media_type: video.media_type,
            url: video.url,
            blurhash: None,
            width: video.width,
            height: video.height,
            filename: video.filename,
            path: video.path,
        })
    }
}

impl std::fmt::Debug for ApAttachment {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApAttachment::Document(doc) => write!(f, "{:?}", doc),
            ApAttachment::Image(img) => write!(f, "{:?}", img),
            ApAttachment::PropertyValue(prop) => write!(f, "{:?}", prop),
            ApAttachment::Video(video) => write!(f, "{:?}", video),
            ApAttachment::Link(link) => write!(f, "{:?}", link),
            ApAttachment::VerifiableIdentityStatement(vis) => write!(f, "{:?}", vis),
        }
    }
}

impl std::fmt::Display for ApAttachment {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ApAttachment::Document(doc) => write!(f, "{}", doc),
            ApAttachment::Image(img) => write!(f, "{}", img),
            ApAttachment::PropertyValue(prop) => write!(f, "{}", prop),
            ApAttachment::Video(video) => write!(f, "{}", video),
            ApAttachment::Link(link) => write!(f, "{}", link),
            ApAttachment::VerifiableIdentityStatement(vis) => write!(f, "{}", vis),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ApDocumentType {
    #[serde(alias = "document")]
    Document,
}

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

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApDocument {
    #[serde(rename = "type")]
    pub kind: ApDocumentType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub blurhash: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<i32>,

    // filename is used internally to facilitate struct initialization
    #[serde(skip_serializing)]
    filename: Option<String>,

    // local_path is used internally to facilitate struct initialization
    #[serde(skip_serializing)]
    path: Option<String>,
}

impl Debug for ApDocument {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}[{}{}{}]",
            self.kind,
            self.url.as_deref().unwrap_or("no_url"),
            match (self.width, self.height) {
                (Some(w), Some(h)) => format!(", {}x{}", w, h),
                _ => String::new(),
            },
            self.media_type
                .as_ref()
                .map(|mt| format!(", {}", mt))
                .unwrap_or_default()
        )
    }
}

impl Display for ApDocument {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}[{}]",
            self.kind,
            self.url.as_deref().unwrap_or("no_url"),
        )
    }
}

impl From<ApDocument> for ApAttachment {
    fn from(document: ApDocument) -> Self {
        ApAttachment::Document(document)
    }
}

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

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

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

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

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

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ApLinkType {
    #[serde(alias = "link")]
    Link,
}

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

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApLink {
    #[serde(rename = "type")]
    pub kind: ApLinkType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub href: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(default)]
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    pub rel: MaybeMultiple<String>,
}

impl Debug for ApLink {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let rel_part = {
            let joined_rels = self
                .rel
                .iter()
                .map(|s| s.to_string())
                .collect::<Vec<String>>()
                .join(",");
            if joined_rels.is_empty() {
                String::new()
            } else {
                format!(", rel:{}", joined_rels)
            }
        };
        write!(
            f,
            "Link[{}{}{}{}]",
            self.href.as_deref().unwrap_or("no_href"),
            self.name
                .as_ref()
                .map(|n| format!(", {}", n))
                .unwrap_or_default(),
            self.media_type
                .as_ref()
                .map(|mt| format!(", {}", mt))
                .unwrap_or_default(),
            rel_part
        )
    }
}

impl Display for ApLink {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "Link[{}]", self.href.as_deref().unwrap_or("no_href"),)
    }
}

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApProof {
    #[serde(rename = "type")]
    pub kind: Option<String>,
    pub created: Option<String>,
    pub proof_purpose: Option<String>,
    pub proof_value: Option<String>,
    pub verification_method: Option<String>,
}

impl Debug for ApProof {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Proof[{}{}{}]",
            self.proof_purpose
                .as_ref()
                .map(|p| format!("purpose: {}", p))
                .unwrap_or_default(),
            self.verification_method
                .as_ref()
                .map(|v| format!(", method: {}", v))
                .unwrap_or_default(),
            self.created
                .as_ref()
                .map(|c| format!(", at: {}", c))
                .unwrap_or_default()
        )
    }
}

impl Display for ApProof {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Proof[{}]",
            self.proof_purpose
                .as_ref()
                .map(|p| format!("purpose: {}", p))
                .unwrap_or_default(),
        )
    }
}

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

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

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApVerifiableIdentityStatement {
    #[serde(rename = "type")]
    pub kind: ApVerifiableIdentityStatementType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub subject: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub proof: Option<ApProof>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub also_known_as: Option<String>,
}

impl Debug for ApVerifiableIdentityStatement {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "VerifiableIdentityStatement[{}{}{}]",
            self.subject
                .as_ref()
                .map(|s| format!("subject: {}", s))
                .unwrap_or_default(),
            self.also_known_as
                .as_ref()
                .map(|aka| format!(", aka: {}", aka))
                .unwrap_or_default(),
            self.proof
                .as_ref()
                .map(|p| format!(", {}", p))
                .unwrap_or_default()
        )
    }
}

impl Display for ApVerifiableIdentityStatement {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "VerifiableIdentityStatement[{}]",
            self.subject
                .as_ref()
                .map(|s| format!("subject: {}", s))
                .unwrap_or_default(),
        )
    }
}
