use std::fmt::{Debug, Display, Formatter};

use crate::{ApActor, ApAddress, ApCollection, ApContext, ApNote, ApObject};
use jdt_maybe_multiple::MaybeMultiple;
use jdt_maybe_reference::MaybeReference;
use serde::{Deserialize, Serialize};

use super::signature::ApSignature;

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub enum ApUpdateType {
    #[default]
    #[serde(alias = "update")]
    Update,
}

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

#[derive(Serialize, Deserialize, Clone, PartialEq, Default)]
#[serde(rename_all = "camelCase")]
pub struct ApUpdate {
    #[serde(rename = "@context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<ApContext>,
    #[serde(rename = "type")]
    pub kind: ApUpdateType,
    pub actor: ApAddress,
    pub id: Option<String>,
    pub object: MaybeReference<ApObject>,
    pub signature: Option<ApSignature>,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub to: MaybeMultiple<ApAddress>,
}

impl Debug for ApUpdate {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Update[actor: {:?}, object: {:?}{}{}]",
            self.actor,
            self.object,
            self.id
                .as_ref()
                .map(|id| format!(", id: {}", id))
                .unwrap_or_default(),
            if !self.to.as_ref().is_empty() {
                format!(", to: {:?}", self.to.as_ref())
            } else {
                String::new()
            }
        )
    }
}

impl Display for ApUpdate {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Update[actor: {}, object: {}{}{}]",
            self.actor,
            self.object,
            self.id
                .as_ref()
                .map(|id| format!(", id: {}", id))
                .unwrap_or_default(),
            if !self.to.as_ref().is_empty() {
                format!(", to: [{}]", self.to)
            } else {
                String::new()
            }
        )
    }
}

impl TryFrom<ApNote> for ApUpdate {
    type Error = &'static str;

    fn try_from(note: ApNote) -> Result<Self, Self::Error> {
        if let Some(id) = note.id.clone() {
            Ok(ApUpdate {
                context: Some(ApContext::default()),
                actor: note.attributed_to.clone(),
                kind: ApUpdateType::default(),
                id: Some(format!("{id}#update")),
                object: MaybeReference::Actual(ApObject::Note(note.clone())),
                signature: None,
                to: note.to,
            })
        } else {
            Err("ApNote must have an ID")
        }
    }
}

impl TryFrom<ApActor> for ApUpdate {
    type Error = &'static str;

    fn try_from(actor: ApActor) -> Result<Self, Self::Error> {
        if let Some(id) = actor.id.clone() {
            Ok(ApUpdate {
                context: Some(ApContext::default()),
                actor: id.clone(),
                kind: ApUpdateType::default(),
                id: Some(format!("{id}#update")),
                object: MaybeReference::Actual(ApObject::Actor(actor)),
                signature: None,
                to: vec![ApAddress::get_public()].into(),
            })
        } else {
            Err("ApActor must have an ID")
        }
    }
}

type ActorCollection = (ApAddress, ApCollection);
impl From<ActorCollection> for ApUpdate {
    fn from((actor, collection): ActorCollection) -> Self {
        Self {
            context: Some(ApContext::default()),
            actor: actor.clone(),
            kind: ApUpdateType::default(),
            id: None,
            object: MaybeReference::Actual(ApObject::Collection(collection)),
            signature: None,
            to: vec![actor].into(),
        }
    }
}
