use std::time::Duration;

use pliantdb_core::kv::{KeyStatus, Kv};
use pliantdb_local::{config::Configuration, Database};

// PliantDb supports a lightweight, atomic key-value store in addition to its
// acid-compliant transactional storage. This interface is meant to replicate
// functionality that you might use something like Redis for -- lightweight
// caching, or fast atomic operations. As with all of PliantDb's core features,
// the Key-Value store is supported across all methods of accessing PliantDb.

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let db = Database::<()>::open_local(
        "key-value-store.pliantdb",
        &Configuration::default(),
    )
    .await?;

    // The set_key method can be awaited to insert/replace a key. Values can be
    // anything supported by serde.
    db.set_key("mykey", &1_u32).await?;

    // Or, you can customize it's behavior:
    let old_value = db
        .set_key("mykey", &2_u32)
        .only_if_exists()
        .returning_previous_as()
        .await?;
    assert_eq!(old_value, Some(1_u32));

    // Retrieving is simple too.
    let value = db.get_key("mykey").into().await?;
    assert_eq!(value, Some(2_u32));

    // Namespacing is built-in as well, so that you can easily separate storage.
    let value = db.with_key_namespace("anamespace").get_key("mykey").await?;
    assert!(value.is_none());

    // Because of the atomic nature of the key-value store, you can use set_key
    // as a synchronized lock:
    let result = db
        .set_key("lockname", &()) // A useful value would be the unique id of the worker that aquired the lock.
        .only_if_vacant()
        .expire_in(Duration::from_millis(100))
        .await?;
    assert_eq!(result, KeyStatus::Inserted);

    let second_try = db
        .set_key("lockname", &())
        .only_if_vacant()
        .expire_in(Duration::from_millis(100))
        .await?;
    assert_eq!(second_try, KeyStatus::NotChanged);

    Ok(())
}
