#![warn(clippy::all, clippy::pedantic, clippy::nursery)]
//! An expressive interface for interacting with a Cache.
//! Inspired by [Laravel's Cache](https://laravel.com/docs/cache) facade.

use drivers::{Driver, Error};
use serde::{de::DeserializeOwned, Serialize};
use std::{sync::Arc, time::Duration};

pub mod drivers;

#[derive(Debug, Clone)]
/// Unified cache interface.
pub struct Cache {
	driver: Arc<dyn Driver>,
}

impl Cache {
	/// Create a new cache backed by the given driver.
	pub fn new<D: Driver + 'static>(driver: D) -> Self {
		Self {
			driver: Arc::new(driver),
		}
	}

	/// Retrieve an item from the cache.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to retrieve the item, or the item cannot be deserialized.
	pub async fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>, Error> {
		let Some(bytes) = self.driver.get(key).await? else {
			return Ok(None);
		};

		bitcode::deserialize(&bytes)
			.map(Some)
			.map_err(|e| Error::Serialization(Box::new(e)))
	}

	/// Check if an item exists in the cache.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to check if the item exists.
	pub async fn has(&self, key: &str) -> Result<bool, Error> {
		self.driver.has(key).await
	}

	/// Retrieve an item from the cache, or store it for some time if it doesn't exist yet.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to retrieve or store the item.
	pub async fn remember<T: Serialize + DeserializeOwned + Send + Sync>(
		&self,
		key: &str,
		duration: Duration,
		value: T,
	) -> Result<T, Error> {
		if let Some(existing) = self.get::<T>(key).await? {
			return Ok(existing);
		}

		self.put(key, &value, duration).await?;

		Ok(value)
	}

	/// Retrieve an item from the cache, or store it forever if it doesn't exist yet.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to retrieve or store the item.
	pub async fn remember_forever<T: Serialize + DeserializeOwned + Send + Sync>(
		&self,
		key: &str,
		value: T,
	) -> Result<T, Error> {
		if let Some(existing) = self.get::<T>(key).await? {
			return Ok(existing);
		}

		self.forever(key, &value).await?;

		Ok(value)
	}

	/// Remove an item from the cache and return it.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to retrieve or remove the item.
	pub async fn pull<T: DeserializeOwned + Send>(&self, key: &str) -> Result<Option<T>, Error> {
		let Some(item) = self.get(key).await? else {
			return Ok(None);
		};

		self.forget(key).await?;

		Ok(Some(item))
	}

	/// Store an item in the cache for a given duration.
	///
	/// # Errors
	///
	/// Returns an error if the value cannot be serialized, or the driver fails to store it.
	pub async fn put<T: Serialize + Sync>(
		&self,
		key: &str,
		value: &T,
		expiry: Duration,
	) -> Result<(), Error> {
		let bytes = bitcode::serialize(value).map_err(|e| Error::Serialization(Box::new(e)))?;

		self.driver.put(key, bytes, Some(expiry)).await
	}

	/// Store an item in the cache if it doesn't exist yet.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to store the item.
	pub async fn add<T: Serialize + Send + Sync>(
		&self,
		key: &str,
		value: T,
		expiry: Duration,
	) -> Result<bool, Error> {
		if self.has(key).await? {
			return Ok(false);
		}

		self.put(key, &value, expiry).await?;

		Ok(true)
	}

	/// Store an item in the cache indefinitely.
	///
	/// # Errors
	///
	/// Returns an error if the value cannot be serialized, or the driver fails to store it.
	pub async fn forever<T: Serialize + Send + Sync>(
		&self,
		key: &str,
		value: &T,
	) -> Result<(), Error> {
		let bytes = bitcode::serialize(value).map_err(|e| Error::Serialization(Box::new(e)))?;

		self.driver.put(key, bytes, None).await
	}

	/// Remove an item from the cache.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to remove the item.
	pub async fn forget(&self, key: &str) -> Result<(), Error> {
		self.driver.forget(key).await
	}

	/// Remove all items from the cache.
	///
	/// # Errors
	///
	/// Returns an error if the driver fails to flush the cache.
	pub async fn flush(&self) -> Result<(), Error> {
		self.driver.flush().await
	}
}