//! Traits describing the fetching of objects

use crate::session::Session;

use std::{rc::Rc, sync::Arc};
use url::Url;

/// A Repository from which objects can be dereferenced
///
/// This can describe both HTTP remotes, e.g. for fetching remote ActivityPub objects, as well as
/// for fetching local objects from a database
///
/// ```rust
/// use apub_core::{repo::{Dereference, Repo}, session::Session};
/// use std::{collections::HashMap, sync::{Arc, Mutex}};
/// use url::Url;
///
/// /// An in-memory repository for objects
/// pub struct MemoryRepo {
///     inner: Arc<Mutex<HashMap<Url, serde_json::Value>>>,
/// }
///
/// #[async_trait::async_trait(?Send)]
/// impl Repo for MemoryRepo {
///     type Error = serde_json::Error;
///
///     async fn fetch<D: Dereference, S: Session>(&self, id: D, _: S) -> Result<Option<D::Output>, Self::Error> {
///         if let Some(value) = self.inner.lock().unwrap().get(id.url()) {
///             serde_json::from_value(value.clone()).map(Some)
///         } else {
///             Ok(None)
///         }
///     }
/// }
/// ```
#[async_trait::async_trait(?Send)]
pub trait Repo {
    /// The Error produced by fetching an object
    type Error: 'static;

    /// Fetch the object from the repository
    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error>;
}

/// A type used to produce Repos
pub trait RepoFactory {
    /// The Reop type to be produced
    type Repo: Repo;

    /// Cryptography used to sign requests
    type Crypto;

    /// Produce the Repo
    fn build_repo(&self, crypto: Self::Crypto) -> Self::Repo;
}

/// A trait describing the input and output types of a dereference operation
///
/// For example, a type ObjectId<T> can be created that contains a simple Url, and with a
/// dereference implementation, can be passed to a Repository to fetch the T object it represents
///
/// ```rust
/// use apub_core::repo::{Dereference, Repo};
/// use std::borrow::Cow;
/// use url::Url;
///
/// #[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
/// struct SomeId<'a>(Cow<'a, Url>);
///
/// #[derive(Debug, serde::Deserialize, serde::Serialize)]
/// struct SomeObject {
///     id: SomeId<'static>,
///     content: String,
/// }
///
/// impl<'a> Dereference for SomeId<'a> {
///     type Output = SomeObject;
///
///     fn url(&self) -> &Url {
///         &self.0
///     }
/// }
///
/// async fn fetch<R: Repo>(repo: &R, url: &Url) -> Option<SomeObject> {
///     let id = SomeId(Cow::Borrowed(url));
///     repo.fetch(id, ()).await.ok()?
/// }
/// ```
pub trait Dereference {
    /// The Concrete Type produced by dereferencing
    type Output: serde::de::DeserializeOwned;

    /// Fetch the URL that describes the object
    fn url(&self) -> &Url;
}

impl<'a, T> Dereference for &'a T
where
    T: Dereference,
{
    type Output = T::Output;

    fn url(&self) -> &Url {
        T::url(self)
    }
}

impl<'a, T> Dereference for &'a mut T
where
    T: Dereference,
{
    type Output = T::Output;

    fn url(&self) -> &Url {
        T::url(self)
    }
}

impl<T> Dereference for Box<T>
where
    T: Dereference,
{
    type Output = T::Output;

    fn url(&self) -> &Url {
        T::url(self)
    }
}

impl<T> Dereference for Rc<T>
where
    T: Dereference,
{
    type Output = T::Output;

    fn url(&self) -> &Url {
        T::url(self)
    }
}

impl<T> Dereference for Arc<T>
where
    T: Dereference,
{
    type Output = T::Output;

    fn url(&self) -> &Url {
        T::url(self)
    }
}

#[async_trait::async_trait(?Send)]
impl<'a, T> Repo for &'a T
where
    T: Repo,
{
    type Error = T::Error;

    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error> {
        T::fetch(self, id, session).await
    }
}

#[async_trait::async_trait(?Send)]
impl<'a, T> Repo for &'a mut T
where
    T: Repo,
{
    type Error = T::Error;

    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error> {
        T::fetch(self, id, session).await
    }
}

#[async_trait::async_trait(?Send)]
impl<T> Repo for Box<T>
where
    T: Repo,
{
    type Error = T::Error;

    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error> {
        T::fetch(self, id, session).await
    }
}

#[async_trait::async_trait(?Send)]
impl<T> Repo for Rc<T>
where
    T: Repo,
{
    type Error = T::Error;

    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error> {
        T::fetch(self, id, session).await
    }
}

#[async_trait::async_trait(?Send)]
impl<T> Repo for Arc<T>
where
    T: Repo,
{
    type Error = T::Error;

    async fn fetch<D: Dereference, S: Session>(
        &self,
        id: D,
        session: S,
    ) -> Result<Option<D::Output>, Self::Error> {
        T::fetch(self, id, session).await
    }
}
