use serde::de::Error;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
use std::fmt::Debug;
use std::str::FromStr;

pub mod acct;

pub trait ContentType: Serialize {
    fn mime_type(&self) -> &'static str;

    fn alt_mime_types(&self) -> &[&'static str];
}

macro_rules! content_type {
    ($visib:vis $typ:ident, $expression:expr $(,$alt:expr)*) => {
        #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
        $visib struct $typ;

        impl AsRef<str> for $typ {
            fn as_ref(&self) -> &'static str {
                $expression
            }
        }

        impl Serialize for $typ {
            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                serializer.serialize_str($expression)
            }
        }

        impl ContentType for $typ {
            fn mime_type(&self) -> &'static str {
                $expression
            }

            fn alt_mime_types(&self) -> &[&'static str] {
                &[$($alt),*]
            }
        }

        impl<'de> Deserialize<'de> for $typ {
            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                let content_type = String::deserialize(deserializer)?;

                if matches!(content_type.as_ref(), $expression $(| $alt)*) {
                    Ok(Self)
                } else {
                    Err(Error::custom(format!(
                        "Invalid content type: {content_type}"
                    )))
                }
            }
        }
    };
}

pub mod content_type {
    use crate::web_model::ContentType;
    use serde::de::Error;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    content_type!(pub ContentActivityStreams, "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "application/activity+json", "application/activity+json; charset=utf-8");
    content_type!(pub ContentActivityJson, "application/activity+json", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"", "application/activity+json; charset=utf-8");
    content_type!(pub ContentHtml, "text/html");
    content_type!(pub ContentJson, "application/json");
    content_type!(pub ContentXrdXml, "application/xrd+xml");
    content_type!(pub ContentJrdJson, "application/jrd+json");
    content_type!(pub ContentMultipartFormData, "multipart/form-data");
    content_type!(pub ContentUrlEncoded, "application/x-www-form-urlencoded");
}

macro_rules! link_rel {
    ($visib:vis $typ:ident, $expression:expr) => {
        #[derive(Copy, Clone, Eq, PartialEq, Debug, Default)]
        $visib struct $typ;

        impl AsRef<str> for $typ {
            fn as_ref(&self) -> &'static str {
                $expression
            }
        }

        impl Serialize for $typ {
            fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
                serializer.serialize_str($expression)
            }
        }

        impl Rel for $typ {
            fn rel(&self) -> &'static str {
                $expression
            }
        }

        impl<'de> Deserialize<'de> for $typ {
            fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
                let rel = String::deserialize(deserializer)?;

                if matches!(rel.as_ref(), $expression) {
                    Ok(Self)
                } else {
                    Err(Error::custom(format!(
                        "Invalid rel: {rel}"
                    )))
                }
            }
        }
    };
}

pub trait Rel: Serialize {
    fn rel(&self) -> &'static str;
}

pub mod rel {
    use crate::web_model::Rel;
    use serde::de::Error;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    link_rel!(pub RelWebFingerProfilePage, "http://webfinger.net/rel/profile-page");
    link_rel!(pub RelSelf, "self");
    link_rel!(pub RelNext, "next");
    link_rel!(pub RelPrev, "prev");
    link_rel!(pub RelLrdd, "lrdd");
    link_rel!(pub RelOStatusSubscribe, "http://ostatus.org/schema/1.0/subscribe");
    link_rel!(pub RelNodeInfo20, "http://nodeinfo.diaspora.software/ns/schema/2.0");
    link_rel!(pub RelNodeInfo21, "http://nodeinfo.diaspora.software/ns/schema/2.1");
}

#[derive(Clone, Eq, PartialEq, Debug)]
pub struct ListContaining<T: Clone + Eq + PartialEq + Debug + AsRef<str> + FromStr>(pub T);

impl<T> Serialize for ListContaining<T>
where
    T: Clone + Eq + PartialEq + Debug + AsRef<str> + FromStr,
{
    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
        vec![AsRef::<str>::as_ref(&self.0)].serialize(serializer)
    }
}

impl<'de, T> Deserialize<'de> for ListContaining<T>
where
    T: Clone + Eq + PartialEq + Debug + AsRef<str> + FromStr,
{
    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
        let data = Vec::<Value>::deserialize(deserializer)?;

        let dt = data
            .iter()
            .filter_map(Value::as_str)
            .find_map(|val| T::from_str(val).ok());

        if let Some(value) = dt {
            Ok(ListContaining(value))
        } else {
            Err(Error::custom("Count not find item in list.".to_string()))
        }
    }
}
