//! Named guard registry — register guards by name and retrieve them as Tower layers.
//!
//! # Example
//!
//! ```rust,ignore
//! // At startup, register guards:
//! rok_auth::register("web", JwtGuard::new(auth.clone(), user_provider));
//! rok_auth::register("api", ApiKeyGuard::new(api_secret));
//!
//! // In your router, reference them by name:
//! let web_app = Router::new()
//!     .route("/dashboard", get(dashboard))
//!     .layer(rok_auth::layer("web"));
//!
//! let api_app = Router::new()
//!     .route("/api/me", get(me))
//!     .layer(rok_auth::layer("api"));
//! ```

use std::{
    collections::HashMap,
    sync::{Arc, OnceLock, RwLock},
};

use async_trait::async_trait;
use axum::http::request::Parts;

use crate::{AuthError, Claims};

use super::auth_guard::{Guard, GuardLayer};

// ── Registry ──────────────────────────────────────────────────────────────────

type BoxedGuard = Arc<dyn Guard>;

static REGISTRY: OnceLock<RwLock<HashMap<&'static str, BoxedGuard>>> = OnceLock::new();

fn registry() -> &'static RwLock<HashMap<&'static str, BoxedGuard>> {
    REGISTRY.get_or_init(|| RwLock::new(HashMap::new()))
}

/// Register a [`Guard`] under `name` so it can be referenced via [`layer`].
///
/// Call this once at startup (e.g. in `main`) before building your routers.
/// Re-registering the same name replaces the previous guard.
pub fn register<G: Guard>(name: &'static str, guard: G) {
    registry()
        .write()
        .expect("rok-auth guard registry poisoned")
        .insert(name, Arc::new(guard));
}

/// Build a [`GuardLayer`] for the guard registered under `name`.
///
/// # Panics
///
/// Panics if no guard has been registered for `name`.
pub fn layer(name: &'static str) -> GuardLayer<NamedGuard> {
    let guard = registry()
        .read()
        .expect("rok-auth guard registry poisoned")
        .get(name)
        .cloned()
        .unwrap_or_else(|| panic!("rok-auth: no guard registered for '{name}'"));
    GuardLayer::new(NamedGuard(guard))
}

/// Same as [`layer`] but does not require the guard to be registered —
/// unauthenticated requests are passed through with no `Claims` in extensions.
///
/// # Panics
///
/// Panics if no guard has been registered for `name`.
pub fn optional_layer(name: &'static str) -> GuardLayer<NamedGuard> {
    let guard = registry()
        .read()
        .expect("rok-auth guard registry poisoned")
        .get(name)
        .cloned()
        .unwrap_or_else(|| panic!("rok-auth: no guard registered for '{name}'"));
    GuardLayer::optional(NamedGuard(guard))
}

// ── NamedGuard ────────────────────────────────────────────────────────────────

/// Type-erased wrapper that lets a dynamic [`Arc<dyn Guard>`] satisfy the
/// `Guard: Sized` bound required by [`GuardLayer`].
pub struct NamedGuard(BoxedGuard);

#[async_trait]
impl Guard for NamedGuard {
    async fn authenticate(&self, parts: &Parts) -> Result<Claims, AuthError> {
        self.0.authenticate(parts).await
    }

    async fn revoke(&self, claims: &Claims) -> Result<(), AuthError> {
        self.0.revoke(claims).await
    }
}
