use super::Ephemeral;
use crate::{ApActor, ApAttachment, ApCollection, ApContext, ApDateTime, ApInstrument, ApTag};
use anyhow::Result;
use chrono::Utc;
use jdt_maybe_multiple::MaybeMultiple;
use jdt_maybe_reference::MaybeReference;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::{Debug, Display, Formatter};

use super::actor::ApAddress;

fn deserialize_filter_none_addresses<'de, D>(
    deserializer: D,
) -> Result<MaybeMultiple<ApAddress>, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let maybe_multiple = MaybeMultiple::<ApAddress>::deserialize(deserializer)?;

    match maybe_multiple {
        MaybeMultiple::Single(addr) => {
            if matches!(addr, ApAddress::None) {
                Ok(MaybeMultiple::None)
            } else {
                Ok(MaybeMultiple::Single(addr))
            }
        }
        MaybeMultiple::Multiple(addrs) => {
            let filtered: Vec<ApAddress> = addrs
                .into_iter()
                .filter(|addr| !matches!(addr, ApAddress::None))
                .collect();

            match filtered.len() {
                0 => Ok(MaybeMultiple::None),
                1 => Ok(MaybeMultiple::Single(filtered.into_iter().next().unwrap())),
                _ => Ok(MaybeMultiple::Multiple(filtered)),
            }
        }
        MaybeMultiple::None => Ok(MaybeMultiple::None),
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Default, Eq, PartialEq)]
pub enum ApNoteType {
    #[default]
    #[serde(alias = "note")]
    Note,
    #[serde(alias = "encrypted_note")]
    EncryptedNote,
    #[serde(alias = "vault_note")]
    VaultNote,
}

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

impl From<ApNoteType> for String {
    fn from(kind: ApNoteType) -> String {
        format!("{kind:#?}")
    }
}

impl TryFrom<String> for ApNoteType {
    type Error = &'static str;

    fn try_from(kind: String) -> Result<Self, Self::Error> {
        match kind.as_str() {
            "note" => Ok(ApNoteType::Note),
            "encrypted_note" => Ok(ApNoteType::EncryptedNote),
            "vault_note" => Ok(ApNoteType::VaultNote),
            _ => Err("no match for {kind}"),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct Metadata {
    pub url: String,
    pub twitter_title: Option<String>,
    pub description: Option<String>,
    pub og_description: Option<String>,
    pub og_title: Option<String>,
    pub og_image: Option<String>,
    pub og_site_name: Option<String>,
    pub twitter_image: Option<String>,
    pub og_url: Option<String>,
    pub twitter_description: Option<String>,
    pub published: Option<String>,
    pub twitter_site: Option<String>,
    pub og_type: Option<String>,
}

impl Display for Metadata {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Metadata[{}{}{}{}]",
            self.url,
            self.og_title
                .as_ref()
                .or(self.twitter_title.as_ref())
                .map(|t| format!(" - {}", t))
                .unwrap_or_default(),
            self.og_description
                .as_ref()
                .or(self.twitter_description.as_ref())
                .or(self.description.as_ref())
                .map(|d| format!(" | {:.60}...", d))
                .unwrap_or_default(),
            self.published
                .as_ref()
                .map(|p| format!(" ({})", p))
                .unwrap_or_default()
        )
    }
}

// (Base Site URL, Metadata Hashmap)
type SiteMetadata = (String, HashMap<String, String>);
impl From<SiteMetadata> for Metadata {
    fn from((url, meta): SiteMetadata) -> Self {
        fn sanitize(url: &str, path: Option<String>) -> Option<String> {
            const MAX_URL_LENGTH: usize = 2048; // Define a reasonable max length for a URL

            path.filter(|p| {
                // Check the scheme and host to avoid security-sensitive URLs
                !(p.starts_with("http://localhost") ||
                  p.starts_with("https://localhost") ||
                  p.starts_with("http://127.0.0.1") ||
                  p.starts_with("https://127.0.0.1") ||
                  p.starts_with("http://0.0.0.0") ||
                  p.starts_with("https://0.0.0.0") ||
                  p.starts_with("file:///") ||
                  p.starts_with("javascript:") ||
                  p.starts_with("data:") ||
                  // Add checks for other IP ranges or conditions as needed
                  p.len() > MAX_URL_LENGTH)
            })
            .map(
                |p| match p.starts_with("http://") || p.starts_with("https://") {
                    true => p,
                    false => format!(
                        "{}/{}",
                        url.trim_end_matches('/'),
                        p.trim_start_matches('/')
                    ),
                },
            )
        }

        let og_image = sanitize(&url, meta.get("og:image").cloned());
        let twitter_image = sanitize(&url, meta.get("twitter:image").cloned());

        Metadata {
            url,
            twitter_title: meta.get("twitter:title").cloned(),
            description: meta.get("description").cloned(),
            og_description: meta.get("og:description").cloned(),
            og_title: meta.get("og:title").cloned(),
            og_image,
            og_site_name: meta.get("og:site_name").cloned(),
            twitter_image,
            og_url: meta.get("og:url").cloned(),
            twitter_description: meta.get("twitter:description").cloned(),
            published: meta.get("article:published").cloned(),
            twitter_site: meta.get("twitter:site").cloned(),
            og_type: meta.get("og:type").cloned(),
        }
    }
}

#[derive(Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ApNote {
    #[serde(rename = "@context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<ApContext>,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub tag: MaybeMultiple<ApTag>,
    #[serde(alias = "actor")]
    pub attributed_to: ApAddress,
    pub id: Option<String>,
    #[serde(rename = "type")]
    pub kind: ApNoteType,
    pub to: MaybeMultiple<ApAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    pub published: ApDateTime,
    #[serde(deserialize_with = "deserialize_filter_none_addresses")]
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub cc: MaybeMultiple<ApAddress>,
    #[serde(skip_serializing_if = "MaybeReference::is_none")]
    #[serde(default)]
    pub replies: MaybeReference<ApCollection>,
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub attachment: MaybeMultiple<ApAttachment>,
    #[serde(deserialize_with = "deserialize_filter_none_addresses")]
    #[serde(skip_serializing_if = "MaybeMultiple::is_none")]
    #[serde(default)]
    pub in_reply_to: MaybeMultiple<ApAddress>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sensitive: Option<bool>,
    #[serde(alias = "context")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub conversation: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub content_map: Option<HashMap<String, String>>,

    // We skip serializing here because 'instrument' doesn't belong
    // on a Note object; it's here only to facilitate the Outbox action
    // to move it to the Create activity.
    #[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 Display for ApNote {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Note[{}{}]",
            self.attributed_to,
            self.id
                .as_ref()
                .map(|r| format!(", id: {r}"))
                .unwrap_or(", no id".to_string())
        )
    }
}

impl Debug for ApNote {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Note[{}{}{}{}{}{}]",
            self.attributed_to,
            if let Some(summary) = &self.summary {
                format!(", summary: {}", summary)
            } else if let Some(content_str) = &self.content {
                format!(
                    ", content: {:.60}{}",
                    content_str,
                    if content_str.len() > 60 { "..." } else { "" }
                )
            } else {
                // If both summary and content are None
                ", no summary or content".to_string()
            },
            self.published,
            self.tag,
            self.get_reply_to()
                .map(|r| format!(", reply_to: {}", r))
                .unwrap_or_default(),
            if self.sensitive.unwrap_or(false) {
                ", sensitive"
            } else {
                ""
            }
        )
    }
}

impl Default for ApNote {
    fn default() -> ApNote {
        ApNote {
            context: Some(ApContext::default()),
            tag: MaybeMultiple::None,
            attributed_to: ApAddress::default(),
            id: None,
            kind: ApNoteType::Note,
            to: MaybeMultiple::Multiple(vec![]),
            url: None,
            published: Utc::now().into(),
            cc: MaybeMultiple::None,
            replies: MaybeReference::None,
            attachment: MaybeMultiple::None,
            in_reply_to: MaybeMultiple::None,
            content: None,
            summary: None,
            sensitive: None,
            conversation: None,
            content_map: None,
            instrument: MaybeMultiple::None,
            ephemeral: None,
        }
    }
}

impl From<ApActor> for ApNote {
    fn from(actor: ApActor) -> Self {
        ApNote {
            tag: MaybeMultiple::Multiple(vec![]),
            attributed_to: actor.id.unwrap(),
            id: None,
            kind: ApNoteType::Note,
            to: MaybeMultiple::Multiple(vec![]),
            content: None,
            ..Default::default()
        }
    }
}

impl ApNote {
    pub fn is_encrypted(&self) -> bool {
        matches!(self.kind, ApNoteType::EncryptedNote)
    }

    pub fn to(mut self, to: String) -> Self {
        if let MaybeMultiple::Multiple(v) = self.to {
            let mut t = v;
            t.push(ApAddress::Address(to));
            self.to = MaybeMultiple::Multiple(t);
        }
        self
    }

    pub fn content(mut self, content: String) -> Self {
        self.content = Some(content);
        self
    }

    pub fn get_reply_to(&self) -> Option<ApAddress> {
        let replies = self.in_reply_to.multiple();
        replies.first().cloned()
    }
}
