//! Token metadata storage
//!
//! Persists token metadata and revocation state for internal tokens.

use std::sync::Arc;

use iam_types::{Error, IamError, Result, StorageError, TokenMetadata};

use crate::backend::{Backend, CasResult, JsonStore};

/// Store for token metadata and revocations
pub struct TokenStore {
    backend: Arc<Backend>,
}

impl JsonStore for TokenStore {
    fn backend(&self) -> &Backend {
        &self.backend
    }
}

impl TokenStore {
    /// Create a new token store
    pub fn new(backend: Arc<Backend>) -> Self {
        Self { backend }
    }

    /// Persist token metadata
    pub async fn put(&self, meta: &TokenMetadata) -> Result<u64> {
        self.put_json(meta.storage_key().as_bytes(), meta).await
    }

    /// Get metadata for a token
    pub async fn get(
        &self,
        principal_id: &str,
        token_id: &str,
    ) -> Result<Option<(TokenMetadata, u64)>> {
        let key = format!("iam/tokens/{}/{}", principal_id, token_id);
        self.get_json(key.as_bytes()).await
    }

    /// Revoke a token if it exists
    pub async fn revoke(
        &self,
        principal_id: &str,
        token_id: &str,
        reason: &str,
        now: u64,
    ) -> Result<bool> {
        let key = format!("iam/tokens/{}/{}", principal_id, token_id);
        let current = self.get_json::<TokenMetadata>(key.as_bytes()).await?;

        let (mut meta, version) = match current {
            Some(v) => v,
            None => return Ok(false),
        };

        if meta.revoked {
            return Ok(false);
        }

        meta.revoke(now, reason);
        match self.cas_json(key.as_bytes(), version, &meta).await? {
            CasResult::Success(_) => Ok(true),
            CasResult::Conflict { expected, actual } => {
                Err(Error::Storage(StorageError::CasConflict {
                    expected,
                    actual,
                }))
            }
            CasResult::NotFound => Err(Error::Iam(IamError::InvalidToken(
                "token disappeared during revoke".into(),
            ))),
        }
    }
}
