use crate::{
    ActivityPub, ApAddress, ApContext, ApDateTime, ApInstrument, ApObject, ApSignature, Ephemeral,
    Temporal,
};
use anyhow::{anyhow, Result};
use chrono::{DateTime, Utc};
use jdt_maybe_multiple::MaybeMultiple;
use jdt_maybe_reference::MaybeReference;
use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};

#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
pub enum ApCreateType {
    #[default]
    #[serde(alias = "create")]
    Create,
}

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

impl TryFrom<String> for ApCreateType {
    type Error = anyhow::Error;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        if s.to_lowercase() == "create" {
            Ok(ApCreateType::Create)
        } else {
            Err(anyhow!("not a create type"))
        }
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ApCreate {
    #[serde(rename = "@context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<ApContext>,
    #[serde(rename = "type")]
    pub kind: ApCreateType,
    #[serde(alias = "attributedTo")]
    pub actor: ApAddress,
    pub to: MaybeMultiple<ApAddress>,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub cc: MaybeMultiple<ApAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub id: Option<String>,
    pub object: MaybeReference<ApObject>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub published: Option<ApDateTime>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub signature: Option<ApSignature>,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub instrument: MaybeMultiple<ApInstrument>,

    // These are ephemeral attributes to facilitate client operations
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ephemeral: Option<Ephemeral>,
}

impl Debug for ApCreate {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Create[by: {}, obj: {}{}{}]",
            self.actor,
            self.object,
            self.published
                .as_ref()
                .map(|p| format!(", at: {}", p))
                .unwrap_or_default(),
            if !self.instrument.as_ref().is_empty() {
                format!(", instruments: {:?}", self.instrument.as_ref())
            } else {
                String::new()
            }
        )
    }
}

impl Display for ApCreate {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Create[{}]",
            self.id
                .as_ref()
                .map(|p| format!("id: {}", p))
                .unwrap_or("no id".to_string()),
        )
    }
}

impl Temporal for ApCreate {
    fn published(&self) -> String {
        if let Some(published) = &self.published {
            published.to_string()
        } else if let MaybeReference::Actual(ApObject::Note(note)) = &self.object {
            note.published.clone().to_string()
        } else {
            ActivityPub::time(Utc::now())
        }
    }

    fn created_at(&self) -> Option<DateTime<Utc>> {
        self.ephemeral.clone().and_then(|x| x.created_at)
    }

    fn updated_at(&self) -> Option<DateTime<Utc>> {
        self.ephemeral.clone().and_then(|x| x.updated_at)
    }
}

impl TryFrom<ApObject> for ApCreate {
    type Error = anyhow::Error;

    fn try_from(object: ApObject) -> Result<ApCreate> {
        match object.clone() {
            ApObject::Note(note) => {
                let context = Some(ApContext::default());
                let kind = ApCreateType::default();
                let actor = note.attributed_to;
                let id = None; // The ID is assigned in NewActivity
                let object = object.into();
                let to = note.to;
                let cc = note.cc;
                let signature = None;
                let published = Some(note.published);
                let ephemeral = None;

                let instrument = note.instrument;

                let instrument = match instrument {
                    MaybeMultiple::Single(instrument) => {
                        if instrument.is_mls_welcome() {
                            vec![instrument].into()
                        } else {
                            MaybeMultiple::None
                        }
                    }
                    MaybeMultiple::Multiple(instruments) => instruments
                        .into_iter()
                        .filter(|x| x.is_mls_welcome())
                        .collect::<Vec<ApInstrument>>()
                        .into(),
                    _ => MaybeMultiple::None,
                };

                Ok(ApCreate {
                    context,
                    kind,
                    actor,
                    id,
                    object,
                    to,
                    cc,
                    signature,
                    published,
                    ephemeral,
                    instrument,
                })
            }
            _ => Err(anyhow!("unimplemented object type")),
        }
    }
}
