use std::{sync::Arc, time::Duration};

use async_trait::async_trait;

use crate::lock::{LockError, LockResult};

/// Result of a distributed lock acquisition.
#[derive(Debug, Clone)]
pub struct LockGuard {
    key: String,
    token: Arc<str>,
    ttl: Duration,
}

impl LockGuard {
    /// Creates a guard with an opaque owner token.
    pub fn new(key: impl Into<String>, token: impl Into<Arc<str>>, ttl: Duration) -> Self {
        Self {
            key: key.into(),
            token: token.into(),
            ttl,
        }
    }

    /// Returns the raw backend lock key.
    pub fn key(&self) -> &str {
        &self.key
    }

    /// Returns the configured lock TTL.
    pub fn ttl(&self) -> Duration {
        self.ttl
    }

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

/// Async distributed lock backend.
#[async_trait]
pub trait DistributedLock: Clone + Send + Sync + 'static {
    /// Attempts to acquire a lock and returns `Ok(None)` when the lock is busy.
    async fn acquire(&self, key: &str, ttl: Duration) -> LockResult<Option<LockGuard>>;

    /// Releases a previously acquired lock guard.
    async fn release(&self, guard: &LockGuard) -> LockResult<()>;

    /// Attempts to acquire a lock and returns [`LockError::Busy`] when unavailable.
    async fn try_acquire(&self, key: &str, ttl: Duration) -> LockResult<LockGuard> {
        self.acquire(key, ttl).await?.ok_or(LockError::Busy)
    }
}
