use std::sync::Arc;

use parking_lot::RwLock;
use thiserror::Error;
use tracing::{debug, info};
use vapora_shared::AgentDefinition;

use crate::built_in::{CodeReviewer, DocGenerator, PRMonitor};
use crate::capability::{Capability, CustomCapability};

#[derive(Debug, Error)]
pub enum CapabilityError {
    #[error("Capability not found: {0}")]
    NotFound(String),

    #[error("Capability already registered: {0}")]
    AlreadyRegistered(String),

    #[error("Invalid capability spec: {0}")]
    InvalidSpec(String),
}

/// Thread-safe catalog of capability packages.
///
/// Read-heavy (looked up on every task dispatch), written rarely (at startup
/// or when loading user overrides). Uses `parking_lot::RwLock` for
/// uncontended-read performance.
///
/// # Examples
///
/// ```rust
/// use vapora_capabilities::CapabilityRegistry;
///
/// let registry = CapabilityRegistry::with_built_ins();
/// let def = registry.activate("code-reviewer").unwrap();
/// assert_eq!(def.role, "code_reviewer");
/// ```
#[derive(Debug)]
pub struct CapabilityRegistry {
    capabilities: RwLock<std::collections::HashMap<String, Arc<dyn Capability>>>,
}

impl CapabilityRegistry {
    /// Create an empty registry.
    pub fn new() -> Self {
        Self {
            capabilities: RwLock::new(std::collections::HashMap::new()),
        }
    }

    /// Create a registry pre-populated with all built-in capabilities.
    ///
    /// Built-ins registered: `code-reviewer`, `doc-generator`, `pr-monitor`.
    pub fn with_built_ins() -> Self {
        let registry = Self::new();
        // These are infallible — built-in IDs are unique by construction.
        registry
            .register(CodeReviewer)
            .expect("code-reviewer id collision");
        registry
            .register(DocGenerator)
            .expect("doc-generator id collision");
        registry
            .register(PRMonitor)
            .expect("pr-monitor id collision");

        info!(
            "CapabilityRegistry initialized with {} built-in capabilities",
            registry.len()
        );
        registry
    }

    /// Register a capability. Returns [`CapabilityError::AlreadyRegistered`]
    /// if a capability with the same ID is already present.
    pub fn register<C: Capability + 'static>(&self, capability: C) -> Result<(), CapabilityError> {
        let spec = capability.spec();
        if spec.id.is_empty() {
            return Err(CapabilityError::InvalidSpec(
                "capability id must not be empty".to_string(),
            ));
        }

        let mut map = self.capabilities.write();
        if map.contains_key(&spec.id) {
            return Err(CapabilityError::AlreadyRegistered(spec.id));
        }

        debug!("Registered capability: {}", spec.id);
        map.insert(spec.id, Arc::new(capability));
        Ok(())
    }

    /// Register or replace a capability (idempotent — used for TOML overrides).
    ///
    /// If a capability with the same ID already exists it is replaced silently.
    /// Use this when applying user-supplied configuration overrides.
    pub fn register_or_replace<C: Capability + 'static>(&self, capability: C) {
        let spec = capability.spec();
        debug!("Registering/replacing capability: {}", spec.id);
        self.capabilities
            .write()
            .insert(spec.id, Arc::new(capability));
    }

    /// Replace a built-in capability with a custom override spec.
    ///
    /// Returns [`CapabilityError::NotFound`] if the ID does not exist.
    pub fn override_spec(
        &self,
        id: &str,
        new_spec: crate::capability::CapabilitySpec,
    ) -> Result<(), CapabilityError> {
        if new_spec.id != id {
            return Err(CapabilityError::InvalidSpec(format!(
                "override spec id '{}' does not match target id '{}'",
                new_spec.id, id
            )));
        }

        let mut map = self.capabilities.write();
        if !map.contains_key(id) {
            return Err(CapabilityError::NotFound(id.to_string()));
        }

        debug!("Overriding capability: {}", id);
        map.insert(id.to_string(), Arc::new(CustomCapability(new_spec)));
        Ok(())
    }

    /// Retrieve a capability by ID.
    pub fn get(&self, id: &str) -> Option<Arc<dyn Capability>> {
        self.capabilities.read().get(id).cloned()
    }

    /// Returns `true` if a capability with the given ID is registered.
    pub fn contains(&self, id: &str) -> bool {
        self.capabilities.read().contains_key(id)
    }

    /// List all registered capability IDs.
    pub fn list_ids(&self) -> Vec<String> {
        let mut ids: Vec<String> = self.capabilities.read().keys().cloned().collect();
        ids.sort();
        ids
    }

    /// Return all registered capabilities.
    pub fn list_all(&self) -> Vec<Arc<dyn Capability>> {
        self.capabilities.read().values().cloned().collect()
    }

    /// Number of registered capabilities.
    pub fn len(&self) -> usize {
        self.capabilities.read().len()
    }

    /// Returns `true` if no capabilities are registered.
    pub fn is_empty(&self) -> bool {
        self.capabilities.read().is_empty()
    }

    /// Convert a registered capability to an [`AgentDefinition`].
    ///
    /// This is the primary activation path: look up by ID and produce an
    /// `AgentDefinition` that can be used with
    /// [`vapora_agents::registry::AgentMetadata::new`] and then registered in
    /// the coordinator.
    ///
    /// # Errors
    ///
    /// Returns [`CapabilityError::NotFound`] if no capability with the given ID
    /// is registered.
    pub fn activate(&self, id: &str) -> Result<AgentDefinition, CapabilityError> {
        let cap = self
            .get(id)
            .ok_or_else(|| CapabilityError::NotFound(id.to_string()))?;

        let def = cap.to_agent_definition();
        debug!(
            "Activated capability '{}' → role '{}' (provider: {}, model: {})",
            id, def.role, def.llm_provider, def.llm_model
        );
        Ok(def)
    }
}

impl Default for CapabilityRegistry {
    fn default() -> Self {
        Self::with_built_ins()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::capability::CapabilitySpec;

    fn minimal_spec(id: &str, role: &str) -> CapabilitySpec {
        CapabilitySpec {
            id: id.to_string(),
            display_name: "Test".to_string(),
            description: "Test capability".to_string(),
            agent_role: role.to_string(),
            task_types: vec!["test_task".to_string()],
            system_prompt: "You are a test agent.".to_string(),
            mcp_tools: vec![],
            preferred_provider: "claude".to_string(),
            preferred_model: "claude-sonnet-4-6".to_string(),
            max_tokens: 1024,
            temperature: 0.5,
            priority: 50,
            parallelizable: true,
        }
    }

    #[test]
    fn built_ins_count_is_three() {
        let registry = CapabilityRegistry::with_built_ins();
        assert_eq!(registry.len(), 3);
    }

    #[test]
    fn built_ins_ids_are_present() {
        let registry = CapabilityRegistry::with_built_ins();
        assert!(registry.contains("code-reviewer"));
        assert!(registry.contains("doc-generator"));
        assert!(registry.contains("pr-monitor"));
    }

    #[test]
    fn get_nonexistent_returns_none() {
        let registry = CapabilityRegistry::with_built_ins();
        assert!(registry.get("nonexistent-capability").is_none());
    }

    #[test]
    fn activate_produces_agent_definition_with_system_prompt() {
        let registry = CapabilityRegistry::with_built_ins();
        let def = registry.activate("code-reviewer").unwrap();

        assert_eq!(def.role, "code_reviewer");
        assert!(
            def.system_prompt.is_some(),
            "system_prompt must be populated by activation"
        );
        let prompt = def.system_prompt.unwrap();
        assert!(
            !prompt.is_empty(),
            "system_prompt must not be empty after activation"
        );
    }

    #[test]
    fn activate_nonexistent_returns_not_found() {
        let registry = CapabilityRegistry::with_built_ins();
        let err = registry.activate("does-not-exist").unwrap_err();
        assert!(matches!(err, CapabilityError::NotFound(_)));
    }

    #[test]
    fn register_duplicate_returns_already_registered() {
        let registry = CapabilityRegistry::new();
        let spec = minimal_spec("my-cap", "my_role");
        registry
            .register(CustomCapability(spec.clone()))
            .expect("first registration succeeds");

        let err = registry.register(CustomCapability(spec)).unwrap_err();
        assert!(matches!(err, CapabilityError::AlreadyRegistered(_)));
    }

    #[test]
    fn register_or_replace_overwrites_existing() {
        let registry = CapabilityRegistry::new();
        let spec_v1 = minimal_spec("my-cap", "role_v1");
        let spec_v2 = minimal_spec("my-cap", "role_v2");

        registry.register(CustomCapability(spec_v1)).unwrap();
        registry.register_or_replace(CustomCapability(spec_v2));

        let retrieved = registry.get("my-cap").unwrap();
        assert_eq!(retrieved.spec().agent_role, "role_v2");
    }

    #[test]
    fn override_spec_replaces_built_in_model() {
        let registry = CapabilityRegistry::with_built_ins();
        let mut new_spec = registry.get("code-reviewer").unwrap().spec();
        new_spec.preferred_model = "claude-sonnet-4-6".to_string();

        registry.override_spec("code-reviewer", new_spec).unwrap();

        let updated = registry.activate("code-reviewer").unwrap();
        assert_eq!(updated.llm_model, "claude-sonnet-4-6");
    }

    #[test]
    fn override_spec_wrong_id_returns_invalid() {
        let registry = CapabilityRegistry::with_built_ins();
        let mut spec = registry.get("code-reviewer").unwrap().spec();
        spec.id = "pr-monitor".to_string(); // mismatch

        let err = registry.override_spec("code-reviewer", spec).unwrap_err();
        assert!(matches!(err, CapabilityError::InvalidSpec(_)));
    }

    #[test]
    fn list_ids_is_sorted() {
        let registry = CapabilityRegistry::with_built_ins();
        let ids = registry.list_ids();
        let mut sorted = ids.clone();
        sorted.sort();
        assert_eq!(ids, sorted);
    }

    #[test]
    fn activate_doc_generator_correct_role() {
        let registry = CapabilityRegistry::with_built_ins();
        let def = registry.activate("doc-generator").unwrap();
        assert_eq!(def.role, "documenter");
        assert!(def.system_prompt.is_some());
    }

    #[test]
    fn activate_pr_monitor_correct_role() {
        let registry = CapabilityRegistry::with_built_ins();
        let def = registry.activate("pr-monitor").unwrap();
        assert_eq!(def.role, "monitor");
        assert!(def.system_prompt.is_some());
    }

    #[test]
    fn register_empty_id_returns_invalid() {
        let registry = CapabilityRegistry::new();
        let spec = minimal_spec("", "role");
        let err = registry.register(CustomCapability(spec)).unwrap_err();
        assert!(matches!(err, CapabilityError::InvalidSpec(_)));
    }
}
