use async_trait::async_trait;
use http::header::ACCEPT;
use url::Url;

use crate::{
    algorithms::ptd::Type,
    mf2::{self, types::Document},
};

/// Provides a facade for determining the post type of an value.
pub trait ItemPostTypeResolutionExt {
    /// Computes the distinct post type of this item.
    fn post_type(&self) -> Type;
}

impl ItemPostTypeResolutionExt for crate::mf2::types::Item {
    fn post_type(&self) -> Type {
        crate::algorithms::ptd::resolve_from_object(self.clone()).unwrap_or(Type::Note)
    }
}

#[test]
fn item_post_type_resultion_ext() {
    let item: crate::mf2::types::Item = serde_json::from_value(serde_json::json!({
        "type": ["h-entry"],
        "properties": {
            "photo": [
                "http://example.com/image.jpg"
            ]
        }
    }))
    .unwrap();

    assert_eq!(item.post_type(), Type::Photo);
}

/// Provides a facade for resolving the Microformats2 document for a value.
#[async_trait]
pub trait FetchMF2FromExt {
    /// With the provided [Client][crate::http::Client], obtain the Microformats document of this value.
    async fn fetch_mf2_from<C: crate::http::Client>(
        &self,
        client: C,
    ) -> Result<Document, crate::Error>;
}

#[async_trait]
impl FetchMF2FromExt for Url {
    async fn fetch_mf2_from<C: crate::http::Client>(
        &self,
        client: C,
    ) -> Result<Document, crate::Error> {
        let req = http::Request::get(self.as_str())
            .header(
                ACCEPT,
                "text/html; charset=utf-8, text/mf2+html; charset=utf-8, */*; q=0.6",
            )
            .body(crate::http::Body::default())
            .map_err(crate::Error::Http)?;

        let response = client
            .send_request(req)
            .await?
            .map(|body| body.as_bytes().to_vec());
        Ok(mf2::http::to_mf2_document(response, self.as_str())
            .map_err(crate::Error::from)?)
    }
}

/// Used to convert a value or a list of values into a normalized list of values.
pub mod as_string_or_list {

    use std::{fmt, str::FromStr};

    use serde::{
        de::{self, Visitor},
        ser::SerializeSeq,
        Deserialize, Deserializer, Serialize,
    };

    pub fn deserialize<'de, Item, D, E>(deserializer: D) -> Result<Vec<Item>, D::Error>
    where
        Item: Deserialize<'de> + FromStr<Err = E> + fmt::Debug,
        E: std::error::Error,
        D: Deserializer<'de>,
    {
        struct CocereIntoList;

        impl<'de> Visitor<'de> for CocereIntoList {
            type Value = Vec<String>;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str(
                    "expecting no value, a string or a list of values, all to be made into a list",
                )
            }

            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
            where
                E: de::Error,
            {
                if v.is_empty() {
                    Ok(Vec::default())
                } else {
                    Ok(vec![v.to_string()])
                }
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: de::SeqAccess<'de>,
            {
                let mut values = Vec::with_capacity(seq.size_hint().unwrap_or_default());

                while let Some(value) = seq.next_element()? {
                    values.push(value);
                }

                Ok(values)
            }
        }

        deserializer
            .deserialize_any(CocereIntoList)
            .and_then(|strings| {
                strings.into_iter().try_fold(
                    Vec::default(),
                    |mut acc, string| match Item::from_str(&string) {
                        Ok(v) => {
                            acc.push(v);
                            Ok(acc)
                        }
                        Err(e) => Err(de::Error::custom(e)),
                    },
                )
            })
    }

    pub fn serialize<Item, S>(list: &[Item], serializer: S) -> Result<S::Ok, S::Error>
    where
        Item: Serialize,
        S: serde::Serializer,
    {
        if let Some(value) = list.first().filter(|_| list.len() == 1) {
            value.serialize(serializer)
        } else {
            let mut seq = serializer.serialize_seq(Some(list.len()))?;

            for v in list {
                seq.serialize_element(v)?;
            }

            seq.end()
        }
    }
}

mod test;
