//! LLM Client Factory (Phase-78: T78.9)
//!
//! Creates `LlmClient` instances based on provider configuration.
//! This is the central entry point for obtaining an LLM client.
//!
//! # Usage
//!
//! ```rust,ignore
//! use aifastdb_core::llm::factory::LlmClientFactory;
//!
//! let factory = LlmClientFactory::new();
//! let provider = LlmProvider { brand: ProviderBrand::OpenAI, ... };
//! let client = factory.create_client(provider)?;
//! ```

use std::sync::Arc;

use tracing::info;

use super::client::LlmClient;
use super::error::{LlmError, LlmResult};
use super::types::LlmProvider;
#[cfg(feature = "http-client")]
use super::types::ProviderProtocol;

#[cfg(feature = "http-client")]
use super::openai_client::OpenAiCompatClient;
#[cfg(feature = "http-client")]
use super::gemini_client::GeminiClient;
#[cfg(feature = "http-client")]
use super::anthropic_client::AnthropicClient;

// ============================================================================
// LlmClientFactory
// ============================================================================

/// Factory for creating `LlmClient` instances from provider configurations.
///
/// The factory inspects the provider's `protocol` field and creates the
/// appropriate client implementation:
///
/// - `OpenAiCompat` → `OpenAiCompatClient` (covers OpenAI, DeepSeek, Ollama, Qwen, etc.)
/// - `Gemini` → `GeminiClient`
/// - `Anthropic` → `AnthropicClient`
pub struct LlmClientFactory;

impl LlmClientFactory {
    /// Create a new factory.
    pub fn new() -> Self {
        Self
    }

    /// Create a client for the given provider.
    ///
    /// Returns an `Arc<dyn LlmClient>` that can be shared across tasks.
    pub fn create_client(
        &self,
        provider: LlmProvider,
    ) -> LlmResult<Arc<dyn LlmClient>> {
        info!(
            provider_id = %provider.id,
            brand = ?provider.brand,
            protocol = ?provider.protocol,
            "Creating LLM client"
        );

        self.create_client_inner(provider)
    }

    #[cfg(feature = "http-client")]
    fn create_client_inner(
        &self,
        provider: LlmProvider,
    ) -> LlmResult<Arc<dyn LlmClient>> {
        match provider.protocol {
            ProviderProtocol::OpenAiCompat => {
                let client = OpenAiCompatClient::new(provider)?;
                Ok(Arc::new(client))
            }
            ProviderProtocol::Gemini => {
                let client = GeminiClient::new(provider)?;
                Ok(Arc::new(client))
            }
            ProviderProtocol::Anthropic => {
                let client = AnthropicClient::new(provider)?;
                Ok(Arc::new(client))
            }
            ProviderProtocol::Custom => {
                Err(LlmError::UnsupportedProtocol(
                    "Custom protocol is not supported by the factory. \
                     Register a custom client via ProviderRouter::register_client()"
                        .to_string(),
                ))
            }
        }
    }

    #[cfg(not(feature = "http-client"))]
    fn create_client_inner(
        &self,
        _provider: LlmProvider,
    ) -> LlmResult<Arc<dyn LlmClient>> {
        Err(LlmError::ConfigError(
            "LLM clients require the 'http-client' feature to be enabled. \
             Add `features = [\"llm-gateway\", \"http-client\"]` to your Cargo.toml."
                .to_string(),
        ))
    }

    /// Create clients for multiple providers and register them with a router.
    ///
    /// Returns the number of successfully registered clients.
    pub fn register_providers(
        &self,
        providers: Vec<LlmProvider>,
        models: &[super::types::LlmModel],
        router: &super::router::ProviderRouter,
    ) -> usize {
        let mut count = 0;

        for provider in providers {
            let pid = provider.id.clone();
            match self.create_client(provider) {
                Ok(client) => {
                    router.register_client(&pid, client);

                    // Register model → provider mappings
                    for model in models {
                        if model.provider_id == pid {
                            router.register_model_provider(&model.id, &pid);
                            // Also register by model name (often different from ID)
                            if model.name != model.id {
                                router.register_model_provider(&model.name, &pid);
                            }
                        }
                    }

                    count += 1;
                }
                Err(e) => {
                    tracing::error!(
                        provider_id = %pid,
                        err = %e,
                        "Failed to create LLM client"
                    );
                }
            }
        }

        info!(count, "Registered LLM clients");
        count
    }
}

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

impl std::fmt::Debug for LlmClientFactory {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("LlmClientFactory").finish()
    }
}

// ============================================================================
// Tests
// ============================================================================

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

    #[test]
    fn test_factory_creation() {
        let factory = LlmClientFactory::new();
        let factory2 = LlmClientFactory::default();
        assert_eq!(format!("{:?}", factory), format!("{:?}", factory2));
    }

    #[cfg(not(feature = "http-client"))]
    #[test]
    fn test_factory_without_http_client() {
        let factory = LlmClientFactory::new();
        let provider = LlmProvider::new(
            "test",
            "Test",
            ProviderBrand::OpenAI,
            "https://api.openai.com",
        );
        let result = factory.create_client(provider);
        assert!(result.is_err());
    }
}
