//! Async storage primitive — the single way for capabilities and
//! plugins to persist state.
//!
//! Plugin authors interact with `Store` via `FrameContext::store()` (in
//! capability handlers) or `SetupContext::store()` (in plugin setup).

use std::fmt;
use std::marker::PhantomData;

use async_trait::async_trait;

pub struct TypedKey<T> {
    path:    &'static str,
    _marker: PhantomData<fn() -> T>,
}

impl<T> TypedKey<T> {
    pub const fn new(path: &'static str) -> Self {
        Self { path, _marker: PhantomData }
    }

    pub fn path(&self) -> &str {
        self.path
    }
}

impl<T> Clone for TypedKey<T> {
    fn clone(&self) -> Self { *self }
}
impl<T> Copy for TypedKey<T> {}

impl<T> fmt::Debug for TypedKey<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "TypedKey<{}>({})", std::any::type_name::<T>(), self.path)
    }
}

pub struct TypedPrefix<T> {
    prefix:  &'static str,
    _marker: PhantomData<fn() -> T>,
}

impl<T> TypedPrefix<T> {
    pub const fn new(prefix: &'static str) -> Self {
        Self { prefix, _marker: PhantomData }
    }

    pub fn prefix(&self) -> &str {
        self.prefix
    }

    pub fn key(&self, suffix: impl AsRef<str>) -> String {
        format!("{}/{}", self.prefix, suffix.as_ref())
    }
}

impl<T> Clone for TypedPrefix<T> {
    fn clone(&self) -> Self { *self }
}
impl<T> Copy for TypedPrefix<T> {}

impl<T> fmt::Debug for TypedPrefix<T> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "TypedPrefix<{}>({})", std::any::type_name::<T>(), self.prefix)
    }
}

#[derive(Clone, Debug)]
pub struct StoreScope {
    pub plugin_id: String,
    pub scope:     StoreScopeKind,
    pub sub_scope: Vec<String>,
}

impl StoreScope {
    pub fn new(plugin_id: impl Into<String>, scope: StoreScopeKind) -> Self {
        Self { plugin_id: plugin_id.into(), scope, sub_scope: Vec::new() }
    }
}

#[derive(Clone, Debug)]
pub enum StoreScopeKind {
    Global,
    Campaign(String),
    Session(String),
    User(String),
}

#[derive(Debug)]
pub enum StoreError {
    Backend(String),
    Serialize { what: &'static str, reason: String },
    Deserialize { what: &'static str, reason: String },
    InvalidKey(String),
}

impl fmt::Display for StoreError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Backend(msg) => write!(f, "backend error: {msg}"),
            Self::Serialize { what, reason } => write!(f, "serialize {what}: {reason}"),
            Self::Deserialize { what, reason } => write!(f, "deserialize {what}: {reason}"),
            Self::InvalidKey(key) => write!(f, "invalid key: {key}"),
        }
    }
}

impl std::error::Error for StoreError {}

#[async_trait]
pub trait Store: Send + Sync + std::fmt::Debug {
    async fn put_typed(&self, path: &str, bytes: Vec<u8>) -> Result<(), StoreError>;
    async fn get_typed(&self, path: &str) -> Result<Option<Vec<u8>>, StoreError>;
    async fn delete_typed(&self, path: &str) -> Result<(), StoreError>;
    async fn scan_prefix_typed(&self, prefix: &str) -> Result<Vec<(String, Vec<u8>)>, StoreError>;
    fn scoped(&self, sub_scope: &str) -> Box<dyn Store>;
}

#[cfg(feature = "wire")]
#[async_trait]
pub trait StoreExt: Store {
    async fn put<T>(&self, key: &TypedKey<T>, value: &T) -> Result<(), StoreError>
    where
        T: serde::Serialize + Send + Sync,
    {
        let bytes = postcard::to_allocvec(value).map_err(|e| StoreError::Serialize {
            what:   std::any::type_name::<T>(),
            reason: e.to_string(),
        })?;
        self.put_typed(key.path(), bytes).await
    }

    async fn get<T>(&self, key: &TypedKey<T>) -> Result<Option<T>, StoreError>
    where
        T: serde::de::DeserializeOwned + Send,
    {
        match self.get_typed(key.path()).await? {
            None => Ok(None),
            Some(bytes) => {
                let value = postcard::from_bytes::<T>(&bytes).map_err(|e| StoreError::Deserialize {
                    what:   std::any::type_name::<T>(),
                    reason: e.to_string(),
                })?;
                Ok(Some(value))
            }
        }
    }

    async fn delete<T>(&self, key: &TypedKey<T>) -> Result<(), StoreError> {
        self.delete_typed(key.path()).await
    }

    async fn scan_prefix<T>(
        &self,
        prefix: &TypedPrefix<T>,
    ) -> Result<Vec<(String, T)>, StoreError>
    where
        T: serde::de::DeserializeOwned + Send,
    {
        let raw = self.scan_prefix_typed(prefix.prefix()).await?;
        let mut out = Vec::with_capacity(raw.len());
        for (suffix, bytes) in raw {
            let value = postcard::from_bytes::<T>(&bytes).map_err(|e| StoreError::Deserialize {
                what:   std::any::type_name::<T>(),
                reason: e.to_string(),
            })?;
            out.push((suffix, value));
        }
        Ok(out)
    }
}

#[cfg(feature = "wire")]
impl<S: Store + ?Sized> StoreExt for S {}
