//! Storage trait — implemented by hosts. A namespaced, async key-value interface.
//!
//! See `docs/ARCHITECTURE.md` for the namespace contract. Hosts are responsible for at-rest
//! encryption of the `keystore/` namespace.

use crate::Result;
use std::fmt::Debug;
use std::future::Future;
use std::pin::Pin;

// On native targets the future must be `Send` so it can move across tokio worker threads.
// On wasm32 the runtime is single-threaded and `JsFuture` is `!Send`, so we can't and don't
// need to require it.
#[cfg(not(target_arch = "wasm32"))]
pub type StorageFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + Send + 'a>>;
#[cfg(target_arch = "wasm32")]
pub type StorageFuture<'a, T> = Pin<Box<dyn Future<Output = Result<T>> + 'a>>;

pub trait Storage: Send + Sync + Debug {
    fn get<'a>(&'a self, namespace: &'a str, key: &'a str) -> StorageFuture<'a, Option<Vec<u8>>>;
    fn put<'a>(&'a self, namespace: &'a str, key: &'a str, value: Vec<u8>)
        -> StorageFuture<'a, ()>;
    fn delete<'a>(&'a self, namespace: &'a str, key: &'a str) -> StorageFuture<'a, ()>;
    fn list_keys<'a>(
        &'a self,
        namespace: &'a str,
        prefix: &'a str,
    ) -> StorageFuture<'a, Vec<String>>;
}

#[cfg(feature = "test-utils")]
pub mod memory {
    use super::*;
    use parking_lot::RwLock;
    use std::collections::BTreeMap;

    #[derive(Debug, Default)]
    pub struct MemStorage {
        inner: RwLock<BTreeMap<(String, String), Vec<u8>>>,
    }

    impl Storage for MemStorage {
        fn get<'a>(&'a self, ns: &'a str, k: &'a str) -> StorageFuture<'a, Option<Vec<u8>>> {
            Box::pin(async move { Ok(self.inner.read().get(&(ns.into(), k.into())).cloned()) })
        }
        fn put<'a>(&'a self, ns: &'a str, k: &'a str, v: Vec<u8>) -> StorageFuture<'a, ()> {
            Box::pin(async move {
                self.inner.write().insert((ns.into(), k.into()), v);
                Ok(())
            })
        }
        fn delete<'a>(&'a self, ns: &'a str, k: &'a str) -> StorageFuture<'a, ()> {
            Box::pin(async move {
                self.inner.write().remove(&(ns.into(), k.into()));
                Ok(())
            })
        }
        fn list_keys<'a>(&'a self, ns: &'a str, prefix: &'a str) -> StorageFuture<'a, Vec<String>> {
            Box::pin(async move {
                let g = self.inner.read();
                Ok(g.keys()
                    .filter(|(n, k)| n == ns && k.starts_with(prefix))
                    .map(|(_, k)| k.clone())
                    .collect())
            })
        }
    }
}
