pub mod apikey;
pub mod permissions;

use crate::error::Result;
use crate::types::{ApiKey, NamespaceId, Permission};

/// The result of a successful authentication — the namespace and its granted
/// permissions. Designed to be cached per-connection (TCP) so permission
/// checks on the hot path are a single bitmask AND with no key re-lookup.
#[derive(Debug, Clone)]
pub struct AuthSession {
    pub namespace: NamespaceId,
    pub permissions: PermissionSet,
}

/// Validates API keys and checks per-namespace permissions.
///
/// Implementations must be thread-safe — authentication may be called
/// concurrently from multiple connection handlers.
pub trait Authenticator: Send + Sync {
    /// Validate an API key and return a session with the namespace and permissions.
    ///
    /// TCP connections call this once during the Connect handshake and cache
    /// the returned `AuthSession` for the lifetime of the connection.
    fn authenticate(&self, key: &ApiKey) -> Result<AuthSession>;

    /// Authenticate and check a permission in one shot.
    ///
    /// Convenience for stateless callers (HTTP) that need to validate the key
    /// and verify a permission on every request. Returns `Err(Error::AuthFailed)`
    /// if the key is invalid or does not belong to the specified namespace, or
    /// `Err(Error::PermissionDenied)` if the key is valid but lacks the
    /// required permission.
    fn check_permission(
        &self,
        namespace: &NamespaceId,
        key: &ApiKey,
        perm: Permission,
    ) -> Result<()>;
}

/// Test-only mock authenticator. Accepts a single key→(namespace, permissions)
/// mapping; rejects everything else.
#[cfg(any(test, feature = "test-support"))]
pub struct MockAuthenticator {
    pub namespace: NamespaceId,
    pub key: ApiKey,
    pub permissions: PermissionSet,
}

#[cfg(any(test, feature = "test-support"))]
impl Authenticator for MockAuthenticator {
    fn authenticate(&self, key: &ApiKey) -> Result<AuthSession> {
        if key.0 == self.key.0 {
            Ok(AuthSession {
                namespace: self.namespace.clone(),
                permissions: self.permissions,
            })
        } else {
            Err(crate::error::Error::AuthFailed)
        }
    }

    fn check_permission(
        &self,
        namespace: &NamespaceId,
        key: &ApiKey,
        perm: Permission,
    ) -> Result<()> {
        let session = self.authenticate(key)?;
        if session.namespace != *namespace {
            return Err(crate::error::Error::AuthFailed);
        }
        session.permissions.check(perm)
    }
}

pub use apikey::ApiKeyStore;
pub use permissions::PermissionSet;
