//! Tag mod
//!
//! An event can tag/reference another public key, or another event.
//!
//! It can also use the tag to add all sort of metadata, useful for their own
//! type
use super::{filter::TagValue, Id};
use chrono::{DateTime, Utc};
use serde::{
    de::{self, Deserialize, Deserializer},
    ser::{self, SerializeSeq, Serializer},
};
use std::{
    collections::VecDeque,
    fmt::{self, Display},
};
use url::Url;

/// Marker as defined NIP-10
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Marker {
    /// For top level replies (those replying directly to the root event), only
    /// the "root" marker should be used
    Root,
    /// Denotes the id of the reply event being responded to
    Reply,
    /// Denotes a quoted or reposted event id.
    Mention,
    /// Unknown marker
    Unknown(String),
}

impl Display for Marker {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "{}",
            match self {
                Self::Root => "root",
                Self::Reply => "reply",
                Self::Mention => "mention",
                Self::Unknown(x) => x,
            }
        )
    }
}

impl From<&str> for Marker {
    fn from(marker: &str) -> Self {
        match marker.to_ascii_lowercase().as_str() {
            "root" => Self::Root,
            "reply" => Self::Reply,
            "mention" => Self::Mention,
            _ => Self::Unknown(marker.to_owned()),
        }
    }
}

#[derive(Debug, PartialEq, Eq, Clone)]
/// Relay access type
pub enum RelayAccessType {
    /// Read access
    Read,
    /// Write access
    Write,
    /// Both read and write access
    Both,
}

/// Tags are how events relates to each other.
///
/// So far, there are two standard tags, Events and Public Key. This an event
/// can be related to another, with extra options (like a relayer url, and a pet
/// name).
///
/// Any non standard tag will be parsed as Unknown with a vector of strings as
/// their parameters
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Tag {
    /// Tag another event
    Event(Id, Option<Url>, Option<Marker>),
    /// Tag another public key
    PubKey(Id, Option<Url>, Option<String>),
    /// Tag a relay
    Relay(Url, RelayAccessType),
    /// Tag a hashtag
    Hashtag(String),
    /// Tag with an external content id
    ExternalContentId(String, Option<Url>),
    /// Tag with a title
    Title(String),
    /// Any non standard tag
    Unknown(String, Vec<String>),
    /// Encrypted - NIP-90
    Encrypted,
    /// Expiration - NIP-40
    Expiration(DateTime<Utc>),
    /// Image - NIP-23, NIP-52, NIP-58
    Image(Url, Option<String>),
    /// Zap Goal - NIP-75
    ZapGoal(Id, Option<Url>),
    /// Weird, supported nonetheless
    Empty,
}

impl Tag {
    /// Is the tag a public key?
    pub fn is_pubkey(&self) -> bool {
        matches!(self, Self::PubKey(_, _, _))
    }

    /// Is the tag an event?
    pub fn is_event(&self) -> bool {
        matches!(self, Self::Event(_, _, _))
    }

    /// Get the identifier for the tag
    pub fn get_identifier(&self) -> &str {
        match self {
            Tag::Event(_, _, _) => "e",
            Tag::PubKey(_, _, _) => "p",
            Tag::Relay(_, _) => "r",
            Tag::Hashtag(_) => "t",
            Tag::Title(_) => "title",
            Tag::Encrypted => "encrypted",
            Tag::Image(_, _) => "image",
            Tag::ZapGoal(_, _) => "goal",
            Tag::Expiration(_) => "expiration",
            Tag::ExternalContentId(_, _) => "i",
            Tag::Unknown(u, _) => u,
            Tag::Empty => "",
        }
    }

    /// Get the indexable value of the tag
    pub fn get_indexable_value(&self) -> Option<TagValue> {
        match self {
            Tag::Event(event, _, _) => Some(TagValue::Id(event.clone())),
            Tag::PubKey(key, _, _) => Some(TagValue::Id(key.clone())),
            Tag::ExternalContentId(id, _) => Some(TagValue::String(id.clone())),
            Tag::Hashtag(content) | Tag::Title(content) => Some(TagValue::String(content.clone())),
            Tag::Relay(url, _) => Some(TagValue::String(url.to_string())),
            Tag::Unknown(_, args) => {
                let value = args.get(0).cloned().unwrap_or_default();
                Some(
                    value
                        .as_str()
                        .try_into()
                        .map(TagValue::Id)
                        .unwrap_or_else(|_| TagValue::String(value)),
                )
            }
            Tag::ZapGoal(event, _) => Some(TagValue::Id(event.clone())),
            Tag::Image(image_url, _) => Some(TagValue::String(image_url.to_string())),
            Tag::Empty | Tag::Encrypted | Tag::Expiration(_) => None,
        }
    }

    /// Converts the tag into bytes if possible.
    ///
    /// The bytes can be used to store the tag in a database or to find it in an in-memory index for subscribers
    pub fn into_bytes(self) -> Option<Vec<u8>> {
        Some(
            self.get_indexable_value()?
                .into_bytes(self.get_identifier()),
        )
    }
}

impl ser::Serialize for Tag {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut seq = serializer.serialize_seq(Some(2))?;
        seq.serialize_element(self.get_identifier())?;

        match self {
            Tag::Encrypted => {}
            Tag::Expiration(date) => {
                seq.serialize_element(&date.timestamp())?;
            }
            Tag::Event(event, relayer_url, marker) => {
                seq.serialize_element(&event.to_string())?;
                if let Some(relayer) = &relayer_url {
                    seq.serialize_element(&relayer)?;
                    if let Some(marker) = &marker {
                        seq.serialize_element(&marker.to_string())?;
                    }
                }
            }
            Tag::ZapGoal(event, relayer_url) => {
                seq.serialize_element(&event.to_string())?;
                if let Some(relayer) = &relayer_url {
                    seq.serialize_element(&relayer)?;
                }
            }
            Tag::ExternalContentId(content, url) => {
                seq.serialize_element(content)?;
                if let Some(url) = url {
                    seq.serialize_element(url)?;
                }
            }
            Tag::Hashtag(content) | Tag::Title(content) => {
                seq.serialize_element(content)?;
            }
            Tag::PubKey(key, relayer_url, pet_name) => {
                seq.serialize_element(&key.to_string())?;
                if let Some(relayer) = &relayer_url {
                    seq.serialize_element(relayer)?;
                    if let Some(pet_name) = &pet_name {
                        seq.serialize_element(pet_name)?;
                    }
                }
            }
            Tag::Relay(url, access) => {
                seq.serialize_element(url)?;

                if let Some(access) = match access {
                    RelayAccessType::Read => Some("read"),
                    RelayAccessType::Write => Some("write"),
                    RelayAccessType::Both => None,
                } {
                    seq.serialize_element(access)?;
                }
            }
            Tag::Unknown(_, extra) => {
                for extra in extra.iter() {
                    seq.serialize_element(extra)?;
                }
            }
            Tag::Image(image, dimensions) => {
                seq.serialize_element(image)?;
                if let Some(dimensions) = dimensions {
                    seq.serialize_element(dimensions)?;
                }
            }
            Tag::Empty => unreachable!(),
        }
        seq.end()
    }
}

impl<'de> Deserialize<'de> for Tag {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        let mut parts: VecDeque<String> = Deserialize::deserialize(deserializer)?;

        match parts.len() {
            0 => return Ok(Tag::Empty),
            1 => {
                let tag_name = parts.pop_front().unwrap_or_default();
                match tag_name.as_str() {
                    "encrypted" => return Ok(Tag::Encrypted),
                    _ => {}
                }
                return Ok(Tag::Unknown(tag_name, vec![]));
            }
            _ => {}
        }

        let tag_type = parts.pop_front().unwrap_or_default();
        let default = Tag::Unknown(tag_type.clone(), parts.clone().into());

        let tag: Result<_, D::Error> = match tag_type.as_str() {
            "e" | "goal" | "p" => parts
                .pop_front()
                .ok_or_else::<D::Error, _>(|| de::Error::custom("missing argument"))
                .and_then(|id| id.parse().map_err(de::Error::custom))
                .and_then(|id| {
                    let relayer_url = parts
                        .pop_front()
                        .map(|x| x.parse())
                        .transpose()
                        .map_err(de::Error::custom);

                    relayer_url.map(|relayer_url| {
                        let extra = parts.pop_front();
                        match tag_type.as_str() {
                            "e" => Tag::Event(id, relayer_url, extra.map(|x| x.as_str().into())),
                            "goal" => Tag::ZapGoal(id, relayer_url),
                            "p" => Tag::PubKey(id, relayer_url, extra),
                            _ => unreachable!(),
                        }
                    })
                }),
            "expiration" => {
                let timestamp = parts
                    .pop_front()
                    .ok_or_else(|| de::Error::custom("missing argument"))?
                    .parse::<i64>()
                    .map_err(|_| de::Error::custom("invalid timestamp"))?;

                DateTime::<Utc>::from_timestamp(timestamp, 0)
                    .map(Tag::Expiration)
                    .ok_or_else(|| de::Error::custom("invalid timestamp"))
            }
            "title" => parts
                .pop_front()
                .map(Tag::Title)
                .ok_or_else(|| de::Error::custom("missing argument")),
            "t" => parts
                .pop_front()
                .map(Tag::Hashtag)
                .ok_or_else(|| de::Error::custom("missing argument")),
            "i" => {
                let external_id = parts
                    .pop_front()
                    .ok_or_else::<D::Error, _>(|| de::Error::custom("missing external id"));
                let url = parts
                    .pop_front()
                    .map(|url| url.parse::<Url>().map_err::<D::Error, _>(de::Error::custom))
                    .transpose();

                if let Ok(external_id) = external_id {
                    url.map(|url| Tag::ExternalContentId(external_id, url))
                } else {
                    Err(de::Error::custom("invalid external id"))
                }
            }
            "r" => {
                let url = parts
                    .pop_front()
                    .ok_or_else::<D::Error, _>(|| de::Error::custom("missing url"))
                    .and_then(|url| url.parse().map_err(de::Error::custom));

                let access = parts
                    .pop_front()
                    .map(|x| match x.as_str() {
                        "read" => Ok(RelayAccessType::Read),
                        "write" => Ok(RelayAccessType::Write),
                        _ => Err(de::Error::custom("invalid relay access type")),
                    })
                    .unwrap_or(Ok(RelayAccessType::Both));

                if let Ok(url) = url {
                    access.map(|access| Tag::Relay(url, access))
                } else {
                    Err(de::Error::custom("invalid url"))
                }
            }
            _ => Ok(default.clone()),
        };

        if !parts.is_empty() {
            return Ok(default);
        }

        Ok(tag.unwrap_or(default))
    }
}

#[cfg(test)]
mod test {
    use serde_json::json;

    use super::*;
    use crate::Response;

    #[test]
    fn bug_01() {
        let json = r#"["EVENT","d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39",{"content":"Will fight under the light of lightnings! No darkness on nostr! 🐶🐾😂","created_at":1678685477,"id":"5462822032c16d32267ba40536409fd51ea188b20e7dd5f9e7a0aa5561346f79","kind":1,"pubkey":"8fb140b4e8ddef97ce4b821d247278a1a4353362623f64021484b372f948000c","sig":"62c8e09a0fedd8096a313ef12436b0f0bcad56e9058d4bc12f61ae6094c099bb966dce47efa2d721c5bfdf923614db46d1d1c248105cb99c4ec495292cc875b1","tags":[["e","5f63f9e7d37673e76ddf7448cd67c3d74f9be96c240a40199b59a30db32d7f43"],["e","c70894331986c66a2baf6fc12dd5c86280e4616cee0e57bbee90972ebbb4b735"],["p","ac3fb436a663b25893657f4b6a3d9d2f02d1974bb5ced603f4d0c8ee32d7e0a2"]]}]"#;
        let message: Result<Response, _> = serde_json::from_str(json);
        assert!(message.is_ok());
    }

    #[test]
    fn parse_unexpected_tag() {
        let json = r#"["EVENT","f4e62282fda9c7d93a6e3b03fd1f1a3a34936f74584b0e021edf9659fd7da9d6",{"content":"1678682368","created_at":1678682368,"id":"7506ccd8ce4de835c61c04fd16f8489b2acb8cc052ed6730cba203188dfedf57","kind":30000,"pubkey":"228cc1e37a8fec2eee3dda3a3dbd04a60968086d8f42751a7632499d938eda8f","sig":"73ec91ee5d23a93287700f31274a57d84f46c0aa06523138c8f5b0cf2f20bd8a7db72346ecae9c717d9642cea81d72c35ea95ec615146b1640f62de5e3fbbb69","tags":[["d","chats/null/lastOpened"]]}]"#;
        let message: Result<Response, _> = serde_json::from_str(json);
        assert!(message.is_ok());
    }

    #[test]
    fn serialize_deserialize() {
        let json = vec![
            json!(["p", "d45a98f8988"]),
            json!([
                "p",
                "d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39"
            ]),
            json!([
                "p",
                "8fe53b37518e3dbe9bab26d912292001d8b882de9456b7b08b615f912dc8bf4a",
                "",
                "mention"
            ]),
            json!([
                "e",
                "eb278e983fcedbb0d143c4250c879d078d037586c5dca8e1cf1a104f9846a460",
            ]),
            json!([
                "p",
                "2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9",
            ]),
        ]
        .into_iter()
        .map(|x| serde_json::from_value(x).unwrap())
        .collect::<Vec<Tag>>();

        let expected = vec![
            Tag::Unknown("p".to_owned(), vec![String::from("d45a98f8988")]),
            Tag::PubKey(
                "d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39"
                    .parse()
                    .unwrap(),
                None,
                None,
            ),
            Tag::Unknown(
                "p".to_owned(),
                vec![
                    "8fe53b37518e3dbe9bab26d912292001d8b882de9456b7b08b615f912dc8bf4a".to_owned(),
                    "".to_owned(),
                    "mention".to_owned(),
                ],
            ),
            Tag::Event(
                "eb278e983fcedbb0d143c4250c879d078d037586c5dca8e1cf1a104f9846a460"
                    .parse()
                    .unwrap(),
                None,
                None,
            ),
            Tag::PubKey(
                "2bda4f03446bc1c6ff00594e350a1e7ec57fb9cdc4a7b52694223d56ce0599e9"
                    .parse()
                    .unwrap(),
                None,
                None,
            ),
        ];

        assert_eq!(json, expected);
    }

    #[test]
    fn test_relay() {
        let json = json!(["r", "https://example.com", "read"]);
        assert_eq!(
            Tag::Relay(
                "https://example.com".parse().expect("valid url"),
                RelayAccessType::Read
            ),
            serde_json::from_value(json).expect("valid json"),
        );

        let json = json!(["r", "https://example.com", "write"]);
        assert_eq!(
            Tag::Relay(
                "https://example.com".parse().expect("valid url"),
                RelayAccessType::Write
            ),
            serde_json::from_value(json).expect("valid json"),
        );

        let json = json!(["r", "https://example.com"]);
        assert_eq!(
            Tag::Relay(
                "https://example.com".parse().expect("valid url"),
                RelayAccessType::Both,
            ),
            serde_json::from_value(json).expect("valid json"),
        );
    }

    #[test]
    fn test_relay_invalid_url() {
        let json = json!(["r", "example.com", "read"]);
        assert_eq!(
            Tag::Unknown(
                "r".to_string(),
                vec!["example.com".to_string(), "read".to_string()],
            ),
            serde_json::from_value(json).expect("valid json"),
        );
    }

    #[test]
    fn malformed_tag() {
        let json = vec![
            json!(["r"]),
            json!(["t"]),
            json!(["p"]),
            json!(["e"]),
            json!(["i"]),
            json!(["title"]),
        ];

        for json in json {
            let tag: Tag = serde_json::from_value(json).expect("valid");
            assert!(matches!(tag, Tag::Unknown(_, _)));
        }
    }

    #[test]
    fn test_relay_invalid_access() {
        let json = json!(["r", "https://example.com", "invalid"]);
        assert_eq!(
            Tag::Unknown(
                "r".to_string(),
                vec!["https://example.com".to_string(), "invalid".to_string()],
            ),
            serde_json::from_value(json).expect("valid json"),
        );
    }

    #[test]
    fn hashtag() {
        let json = json!(["t", "rust"]);
        assert_eq!(
            Tag::Hashtag("rust".to_string()),
            serde_json::from_value(json).expect("valid json"),
        );
    }

    #[test]
    fn title() {
        let json = json!(["title", "Rust"]);
        assert_eq!(
            Tag::Title("Rust".to_string()),
            serde_json::from_value(json).expect("valid json"),
        );
    }

    #[test]
    fn external_content_id() {
        let json = json!(["i", "123"]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::ExternalContentId("123".to_string(), None,),
        );
        let json = json!(["i", "123", "https://example.com"]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::ExternalContentId(
                "123".to_string(),
                Some("https://example.com".parse().expect("valid url"))
            ),
        );
    }

    #[test]
    fn external_content_id_invalid_args() {
        let json = json!(["i", "123", "https://example.com", "unexpected"]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::Unknown(
                "i".to_owned(),
                vec![
                    "123".to_owned(),
                    "https://example.com".to_owned(),
                    "unexpected".to_owned()
                ]
            ),
        );
    }

    #[test]
    fn encrypted_tag() {
        let json = json!(["encrypted"]);
        assert_eq!(Tag::Encrypted, serde_json::from_value(json).expect("valid"));
    }

    #[test]
    fn external_content_id_invalid_url() {
        let json = json!(["i", "123", "facebook.com"]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::Unknown(
                "i".to_owned(),
                vec!["123".to_owned(), "facebook.com".to_owned()]
            ),
        );
    }

    #[test]
    fn goal() {
        let json = json!([
            "goal",
            "d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39"
        ]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::ZapGoal(
                "d45a98f898820258a3313f5cb14f5fe8a9263437931ac6309f23ae0324833f39"
                    .parse()
                    .unwrap(),
                None
            ),
        );
    }

    #[test]
    fn timestamp() {
        let json = json!(["expiration", "1678682368"]);
        assert_eq!(
            serde_json::from_value::<Tag>(json).expect("valid"),
            Tag::Expiration(DateTime::<Utc>::from_timestamp(1678682368, 0).unwrap()),
        );
    }
}
