//! Aspect registry — register built-in and custom aspects by name.
//!
//! The compiler's AOP codegen uses the registry to look up aspect implementations
//! by their decorator name (e.g., `"authenticate"` → `AuthenticateAspect`).
//!
//! ## Security-by-default
//!
//! `"public"` is a special bypass marker — NOT an aspect. When the compiler sees
//! `@public` it calls `registry.is_public_bypass(name)` and skips default auth.
//!
//! `set_default_auth_guard()` installs the global default `AuthenticateAspect`
//! that is prepended to every non-`@public` chain.

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

type AspectFactory = Box<dyn Fn(&HashMap<String, String>) -> Arc<dyn Aspect> + Send + Sync>;

/// Registry of aspect factories, indexed by decorator name.
pub struct AspectRegistry {
    factories: RwLock<HashMap<String, AspectFactory>>,
    /// When `true`, the registry is configured to prepend AuthenticateAspect
    /// to every non-`@public` chain via `AspectChain::with_default_auth()`.
    default_auth_enabled: RwLock<bool>,
    /// Names that are bypass markers (not real aspects). Currently just "public".
    bypass_markers: RwLock<std::collections::HashSet<String>>,
}

impl AspectRegistry {
    pub fn new() -> Self {
        let mut markers = std::collections::HashSet::new();
        markers.insert("public".to_string());
        Self {
            factories: RwLock::new(HashMap::new()),
            default_auth_enabled: RwLock::new(false),
            bypass_markers: RwLock::new(markers),
        }
    }

    /// Create a registry with all built-in aspects registered.
    pub fn with_builtins() -> Self {
        let registry = Self::new();
        registry.register_builtins();
        registry
    }

    /// Enable security-by-default: every non-`@public` chain will have
    /// `AuthenticateAspect` prepended automatically.
    ///
    /// Call at application startup. After this, use `AspectChain::with_default_auth()`
    /// when building chains for protected functions.
    pub fn set_default_auth_enabled(&self, enabled: bool) {
        *self.default_auth_enabled.write().expect("registry lock poisoned") = enabled;
    }

    /// Returns `true` if security-by-default auth is active.
    pub fn is_default_auth_enabled(&self) -> bool {
        *self.default_auth_enabled.read().expect("registry lock poisoned")
    }

    /// Returns `true` if `name` is a public bypass marker (e.g., `"public"`).
    ///
    /// Bypass markers are NOT aspects — they signal that default auth should
    /// be skipped for the decorated function.
    pub fn is_public_bypass(&self, name: &str) -> bool {
        self.bypass_markers
            .read()
            .expect("registry lock poisoned")
            .contains(name)
    }

    /// Register a custom bypass marker name.
    ///
    /// By default only `"public"` is registered. Use this to add custom
    /// bypass annotations (e.g., `"internal_only"` that uses a different guard).
    pub fn register_bypass_marker(&self, name: &str) {
        self.bypass_markers
            .write()
            .expect("registry lock poisoned")
            .insert(name.to_string());
    }

    /// Build an `AspectChain` for a function with the given decorators.
    ///
    /// This is the primary chain-building entry point used by the AOP codegen.
    ///
    /// - If any decorator is a bypass marker (`@public`), returns a plain chain
    ///   with no default auth.
    /// - Otherwise, if `default_auth_enabled`, prepends `AuthenticateAspect`.
    /// - Unknown decorator names are silently skipped (forward-compatible).
    pub fn build_chain(&self, decorator_names: &[(&str, HashMap<String, String>)]) -> crate::AspectChain {
        let is_public = decorator_names.iter().any(|(name, _)| self.is_public_bypass(name));

        let mut chain = crate::AspectChain::new();

        for (name, params) in decorator_names {
            if self.is_public_bypass(name) {
                continue; // bypass markers are not aspects
            }
            if let Some(aspect) = self.create(name, params) {
                chain = chain.add(aspect);
            }
        }

        if !is_public && self.is_default_auth_enabled() {
            chain = chain.with_default_auth();
        }

        chain
    }

    /// Register all built-in aspects.
    pub fn register_builtins(&self) {
        use crate::aspects::*;

        self.register("authenticate", |_params| {
            Arc::new(AuthenticateAspect)
        });

        self.register("authorize", |params| {
            let role = params
                .get("role")
                .cloned()
                .unwrap_or_else(|| "user".to_string());
            Arc::new(AuthorizeAspect::new(role))
        });

        self.register("cache", |params| {
            let ttl: u64 = params
                .get("ttl")
                .and_then(|s| s.parse().ok())
                .unwrap_or(300);
            Arc::new(CacheAspect::new(ttl))
        });

        self.register("rate_limit", |params| {
            let requests: u32 = params
                .get("requests")
                .and_then(|s| s.parse().ok())
                .unwrap_or(100);
            let per: u64 = params
                .get("per")
                .and_then(|s| s.parse().ok())
                .unwrap_or(60);
            Arc::new(RateLimitAspect::new(requests, per))
        });

        self.register("log", |params| {
            let level = params
                .get("level")
                .cloned()
                .unwrap_or_else(|| "info".to_string());
            Arc::new(LogAspect::new(level))
        });

        self.register("validate", |_params| Arc::new(ValidateAspect::new()));

        self.register("retry", |params| {
            let attempts: u32 = params
                .get("attempts")
                .and_then(|s| s.parse().ok())
                .unwrap_or(3);
            let backoff = params
                .get("backoff")
                .map(|s| s.as_str())
                .unwrap_or("none");
            let aspect = RetryAspect::new(attempts);
            let aspect = match backoff {
                "exponential" => aspect.with_exponential_backoff(100),
                "fixed" => aspect.with_fixed_backoff(500),
                _ => aspect,
            };
            Arc::new(aspect)
        });

        self.register("trace", |params| {
            let service = params
                .get("service")
                .cloned()
                .unwrap_or_else(|| "el-ui".to_string());
            Arc::new(TraceAspect::new(service))
        });
    }

    /// Register a custom aspect factory.
    pub fn register(
        &self,
        name: &str,
        factory: impl Fn(&HashMap<String, String>) -> Arc<dyn Aspect> + Send + Sync + 'static,
    ) {
        self.factories
            .write()
            .expect("registry lock poisoned")
            .insert(name.to_string(), Box::new(factory));
    }

    /// Instantiate an aspect by decorator name with the given params.
    pub fn create(
        &self,
        name: &str,
        params: &HashMap<String, String>,
    ) -> Option<Arc<dyn Aspect>> {
        let factories = self.factories.read().expect("registry lock poisoned");
        factories.get(name).map(|f| f(params))
    }

    /// List all registered aspect names.
    pub fn aspect_names(&self) -> Vec<String> {
        self.factories
            .read()
            .expect("registry lock poisoned")
            .keys()
            .cloned()
            .collect()
    }

    /// Check if an aspect name is registered.
    pub fn contains(&self, name: &str) -> bool {
        self.factories
            .read()
            .expect("registry lock poisoned")
            .contains_key(name)
    }
}

impl Default for AspectRegistry {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod registry_default_auth_tests {
    use super::*;

    #[test]
    fn test_default_auth_disabled_by_default() {
        let reg = AspectRegistry::new();
        assert!(!reg.is_default_auth_enabled());
    }

    #[test]
    fn test_set_default_auth_enabled() {
        let reg = AspectRegistry::new();
        reg.set_default_auth_enabled(true);
        assert!(reg.is_default_auth_enabled());
    }

    #[test]
    fn test_public_is_bypass_marker() {
        let reg = AspectRegistry::new();
        assert!(reg.is_public_bypass("public"));
        assert!(!reg.is_public_bypass("authenticate"));
    }

    #[test]
    fn test_build_chain_public_skips_default_auth() {
        let reg = AspectRegistry::with_builtins();
        reg.set_default_auth_enabled(true);
        let decorators = vec![("public", HashMap::new())];
        let chain = reg.build_chain(&decorators);
        assert!(!chain.has_auth(), "public chain should not have default auth");
    }

    #[test]
    fn test_build_chain_non_public_gets_default_auth() {
        let reg = AspectRegistry::with_builtins();
        reg.set_default_auth_enabled(true);
        let decorators = vec![("log", HashMap::new())];
        let chain = reg.build_chain(&decorators);
        assert!(chain.has_auth(), "non-public chain should have default auth prepended");
    }

    #[test]
    fn test_build_chain_auth_is_first_aspect() {
        let reg = AspectRegistry::with_builtins();
        reg.set_default_auth_enabled(true);
        let decorators = vec![("log", HashMap::new())];
        let chain = reg.build_chain(&decorators);
        let names = chain.aspect_names();
        assert_eq!(names[0], "authenticate", "authenticate must be first in the chain");
    }
}
