//! Vendor registry for looking up vendor implementations
//!
//! The registry provides a centralized way to look up vendors by ID, alias,
//! or ConnectorAgentType.

use once_cell::sync::Lazy;
use std::collections::HashMap;

use super::{ClaudeVendor, CodexVendor, CustomVendor, GeminiVendor, VendorInfo};
use crate::connectors::acp::config::ConnectorAgentType;

/// Global vendor registry instance
pub static VENDOR_REGISTRY: Lazy<VendorRegistry> = Lazy::new(VendorRegistry::new);

/// Registry of all known vendors
pub struct VendorRegistry {
    /// Vendors indexed by their primary ID
    vendors: HashMap<&'static str, Box<dyn VendorInfo>>,
    /// Alias to vendor ID mapping
    aliases: HashMap<&'static str, &'static str>,
}

impl VendorRegistry {
    /// Create a new vendor registry with all known vendors
    pub fn new() -> Self {
        let mut registry = Self {
            vendors: HashMap::new(),
            aliases: HashMap::new(),
        };

        // Register all vendors
        registry.register(Box::new(ClaudeVendor));
        registry.register(Box::new(CodexVendor));
        registry.register(Box::new(GeminiVendor));
        registry.register(Box::new(CustomVendor));

        registry
    }

    /// Register a vendor in the registry
    fn register(&mut self, vendor: Box<dyn VendorInfo>) {
        let id = vendor.id();

        // Register aliases
        for alias in vendor.aliases() {
            self.aliases.insert(alias, id);
        }

        // Register the vendor
        self.vendors.insert(id, vendor);
    }

    /// Look up a vendor by ID or alias
    pub fn get(&self, id_or_alias: &str) -> Option<&dyn VendorInfo> {
        let id = self
            .aliases
            .get(id_or_alias)
            .copied()
            .unwrap_or(id_or_alias);
        self.vendors.get(id).map(|v| v.as_ref())
    }

    /// Look up a vendor by ConnectorAgentType
    pub fn get_by_agent_type(&self, agent_type: ConnectorAgentType) -> Option<&dyn VendorInfo> {
        match agent_type {
            ConnectorAgentType::Claude => self.get("claude"),
            ConnectorAgentType::Codex => self.get("codex"),
            ConnectorAgentType::Gemini => self.get("gemini"),
            ConnectorAgentType::Custom => self.get("custom"),
        }
    }

    /// List all registered vendors
    pub fn list_all(&self) -> Vec<&dyn VendorInfo> {
        self.vendors.values().map(|v| v.as_ref()).collect()
    }

    /// List vendor IDs
    pub fn list_ids(&self) -> Vec<&'static str> {
        self.vendors.keys().copied().collect()
    }

    /// Check if a vendor CLI is available on the system
    #[cfg(not(target_arch = "wasm32"))]
    pub fn is_cli_available(&self, vendor_id: &str) -> bool {
        use std::process::Command;

        let Some(vendor) = self.get(vendor_id) else {
            return false;
        };

        let command = vendor.cli_command();
        if command.is_empty() {
            return false;
        }

        // Use 'which' on Unix, 'where' on Windows
        #[cfg(target_os = "windows")]
        let check_cmd = "where";
        #[cfg(not(target_os = "windows"))]
        let check_cmd = "which";

        Command::new(check_cmd)
            .arg(command)
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false)
    }

    /// Detect which vendor CLIs are available on the system
    #[cfg(not(target_arch = "wasm32"))]
    pub fn detect_available(&self) -> Vec<&dyn VendorInfo> {
        self.vendors
            .values()
            .filter(|v| {
                let cmd = v.cli_command();
                !cmd.is_empty() && self.is_cli_available(v.id())
            })
            .map(|v| v.as_ref())
            .collect()
    }
}

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

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

    #[test]
    fn test_get_by_id() {
        let registry = VendorRegistry::new();

        let claude = registry.get("claude").unwrap();
        assert_eq!(claude.id(), "claude");
        assert_eq!(claude.display_name(), "Claude");

        let codex = registry.get("codex").unwrap();
        assert_eq!(codex.id(), "codex");

        let gemini = registry.get("gemini").unwrap();
        assert_eq!(gemini.id(), "gemini");

        let custom = registry.get("custom").unwrap();
        assert_eq!(custom.id(), "custom");
    }

    #[test]
    fn test_get_by_alias() {
        let registry = VendorRegistry::new();

        // "anthropic" should resolve to "claude"
        let claude = registry.get("anthropic").unwrap();
        assert_eq!(claude.id(), "claude");

        // "openai" should resolve to "codex"
        let codex = registry.get("openai").unwrap();
        assert_eq!(codex.id(), "codex");

        // "google" should resolve to "gemini"
        let gemini = registry.get("google").unwrap();
        assert_eq!(gemini.id(), "gemini");
    }

    #[test]
    fn test_get_by_agent_type() {
        let registry = VendorRegistry::new();

        let claude = registry
            .get_by_agent_type(ConnectorAgentType::Claude)
            .unwrap();
        assert_eq!(claude.id(), "claude");

        let codex = registry
            .get_by_agent_type(ConnectorAgentType::Codex)
            .unwrap();
        assert_eq!(codex.id(), "codex");

        let gemini = registry
            .get_by_agent_type(ConnectorAgentType::Gemini)
            .unwrap();
        assert_eq!(gemini.id(), "gemini");

        let custom = registry
            .get_by_agent_type(ConnectorAgentType::Custom)
            .unwrap();
        assert_eq!(custom.id(), "custom");
    }

    #[test]
    fn test_list_all() {
        let registry = VendorRegistry::new();
        let vendors = registry.list_all();

        assert_eq!(vendors.len(), 4);

        let ids: Vec<_> = vendors.iter().map(|v| v.id()).collect();
        assert!(ids.contains(&"claude"));
        assert!(ids.contains(&"codex"));
        assert!(ids.contains(&"gemini"));
        assert!(ids.contains(&"custom"));
    }

    #[test]
    fn test_unknown_vendor() {
        let registry = VendorRegistry::new();
        assert!(registry.get("unknown").is_none());
    }

    #[test]
    fn test_global_registry() {
        // Test the static VENDOR_REGISTRY
        let claude = VENDOR_REGISTRY.get("claude").unwrap();
        assert_eq!(claude.id(), "claude");
    }
}
