//! Generator implementations — 将 assembled messages 送入 LLM 生成回答
//!
//! 提供两种 Generator 实现：
//!
//! 1. **ExternalLlmGenerator**: 通过 `HttpApiClient` 调用外部 LLM API（OpenAI / Ollama / Anthropic）
//! 2. **MicroGptGenerator**: 使用内置的 `MicroGpt` 引擎做本地推理（需要 `candle` feature）
//!
//! 两者都实现 `Generator` trait，可互换使用。

use super::error::{RagError, RagResult};
use super::traits::Generator;
use super::types::{ChatMessage, RagConfig, RagResponse, RagTokenUsage};

// ============================================================================
// ExternalLlmGenerator
// ============================================================================

/// Generator that delegates to an external LLM via HTTP API.
///
/// Wraps `HttpApiClient` (OpenAI-compatible endpoint) to perform chat completion.
/// This is the recommended generator for production use.
///
/// # Example
///
/// ```ignore
/// use aifastdb_core::perception::{ReasoningConfig, HttpApiClient};
/// use aifastdb_core::rag::ExternalLlmGenerator;
///
/// let config = ReasoningConfig::ollama("llama3.2");
/// let client = HttpApiClient::from_config(&config)?;
/// let generator = ExternalLlmGenerator::new(client);
/// ```
pub struct ExternalLlmGenerator {
    /// The model name (used in response metadata)
    model_name: String,
    /// Closure that performs the actual LLM call.
    /// We use a trait-object approach so we don't depend on `http-client` feature at compile time.
    chat_fn: Box<dyn Fn(&[crate::perception::Message]) -> Result<crate::perception::ReasoningOutput, crate::perception::ReasoningError> + Send + Sync>,
}

impl ExternalLlmGenerator {
    /// Create from any `ReasoningEngine` implementation.
    ///
    /// This accepts `HttpApiClient`, or any custom `ReasoningEngine`.
    pub fn from_reasoning_engine(
        engine: Box<dyn crate::perception::ReasoningEngine>,
        model_name: impl Into<String>,
    ) -> Self {
        let model = model_name.into();
        Self {
            model_name: model,
            chat_fn: Box::new(move |messages| engine.chat(messages)),
        }
    }

    /// Create from a shared `ReasoningEngine` (Arc).
    pub fn from_shared_engine(
        engine: std::sync::Arc<dyn crate::perception::ReasoningEngine>,
        model_name: impl Into<String>,
    ) -> Self {
        let model = model_name.into();
        Self {
            model_name: model,
            chat_fn: Box::new(move |messages| engine.chat(messages)),
        }
    }
}

impl Generator for ExternalLlmGenerator {
    fn generate(
        &self,
        messages: &[ChatMessage],
        _config: &RagConfig,
    ) -> RagResult<RagResponse> {
        // Convert ChatMessage → perception::Message
        let api_messages: Vec<crate::perception::Message> =
            messages.iter().map(crate::perception::Message::from).collect();

        // Call the LLM
        let result = (self.chat_fn)(&api_messages)
            .map_err(|e| RagError::Generation(format!("LLM call failed: {}", e)))?;

        // Build response
        let usage = result.usage.map(|u| RagTokenUsage {
            prompt_tokens: u.prompt_tokens,
            completion_tokens: u.completion_tokens,
            total_tokens: u.total_tokens,
            context_tokens: 0, // filled by pipeline
            cost: None,
            reasoning_tokens: None,
        });

        let mut metadata = std::collections::HashMap::new();
        if let Some(ref model) = result.model {
            metadata.insert(
                "model".to_string(),
                serde_json::json!(model),
            );
        }

        Ok(RagResponse {
            answer: result.content,
            sources: Vec::new(), // filled by pipeline
            session_id: None,
            usage,
            retrieval_count: 0,
            latency_ms: None,
            metadata,
        })
    }
}

// ============================================================================
// MicroGptGenerator
// ============================================================================

/// Generator that uses the built-in MicroGPT engine for local inference.
///
/// This is useful for:
/// - Offline / air-gapped deployments
/// - Low-latency responses (no network round-trip)
/// - Privacy-sensitive use cases
///
/// Note: MicroGPT is a lightweight model — quality may be lower than large external LLMs.
///
/// # Requires
///
/// - `candle` feature enabled
/// - A trained MicroGPT model (via `ModelRegistry`)
#[cfg(feature = "candle")]
pub struct MicroGptGenerator {
    /// The model registry for loading/generating
    registry: std::sync::Arc<crate::gpt::ModelRegistry>,
    /// Model name to use for generation
    model_name: String,
}

#[cfg(feature = "candle")]
impl MicroGptGenerator {
    /// Create a new MicroGPT generator.
    pub fn new(
        registry: std::sync::Arc<crate::gpt::ModelRegistry>,
        model_name: impl Into<String>,
    ) -> Self {
        Self {
            registry,
            model_name: model_name.into(),
        }
    }
}

#[cfg(feature = "candle")]
impl Generator for MicroGptGenerator {
    fn generate(
        &self,
        messages: &[ChatMessage],
        config: &RagConfig,
    ) -> RagResult<RagResponse> {
        // Flatten messages into a single prompt string
        let prompt = messages
            .iter()
            .map(|m| match m.role {
                super::types::ChatRole::System => format!("[System]\n{}\n", m.content),
                super::types::ChatRole::User => format!("[User]\n{}\n", m.content),
                super::types::ChatRole::Assistant => format!("[Assistant]\n{}\n", m.content),
            })
            .collect::<Vec<_>>()
            .join("\n");

        let prompt_with_prefix = format!("{}[Assistant]\n", prompt);

        // Generate using MicroGPT
        let gen_config = crate::gpt::GenerateConfig {
            max_new_tokens: config.max_generation_tokens,
            temperature: config.temperature as f64,
            top_k: 40,
        };
        let generated = self
            .registry
            .generate(&self.model_name, &prompt_with_prefix, &gen_config)
            .map_err(|e| RagError::Generation(format!("MicroGPT generation failed: {}", e)))?;

        let prompt_tokens = (prompt_with_prefix.len() + 3) / 4; // rough estimate
        let completion_tokens = (generated.len() + 3) / 4;

        Ok(RagResponse {
            answer: generated,
            sources: Vec::new(),
            session_id: None,
            usage: Some(RagTokenUsage {
                prompt_tokens,
                completion_tokens,
                total_tokens: prompt_tokens + completion_tokens,
                context_tokens: 0,
                cost: None,
                reasoning_tokens: None,
            }),
            retrieval_count: 0,
            latency_ms: None,
            metadata: {
                let mut m = std::collections::HashMap::new();
                m.insert(
                    "model".to_string(),
                    serde_json::json!(self.model_name),
                );
                m.insert(
                    "backend".to_string(),
                    serde_json::json!("microgpt"),
                );
                m
            },
        })
    }
}

// ============================================================================
// LlmGatewayGenerator (Phase-82: T82.1)
// ============================================================================

/// Generator that delegates to the LLM Gateway's `ProviderRouter`.
///
/// This is the recommended generator for production use when the LLM Gateway
/// module is enabled. It benefits from:
///
/// - **Multi-provider routing**: Automatically routes to the best available provider
/// - **Failover**: Tries alternative providers if the primary fails
/// - **Unified protocol**: Works with OpenAI, Gemini, Anthropic, Ollama, etc.
/// - **Token/cost tracking**: Full token usage + cost tracking via `ModelPricing`
/// - **Health monitoring**: Unhealthy providers are automatically skipped
///
/// # Requires
///
/// - `llm-gateway` feature enabled
///
/// # Example
///
/// ```ignore
/// use aifastdb_core::llm::router::ProviderRouter;
/// use aifastdb_core::rag::LlmGatewayGenerator;
/// use std::sync::Arc;
///
/// let router = Arc::new(ProviderRouter::default_router());
/// // ... register providers & model mappings ...
/// let generator = LlmGatewayGenerator::new(router, "qwen2.5:14b");
/// ```
#[cfg(feature = "llm-gateway")]
pub struct LlmGatewayGenerator {
    /// Provider router for model routing + failover.
    router: std::sync::Arc<crate::llm::router::ProviderRouter>,
    /// Model name used for routing (e.g., "gpt-4o", "qwen2.5:14b").
    model_name: String,
    /// Whether to use failover routing (try multiple providers on failure).
    use_failover: bool,
}

#[cfg(feature = "llm-gateway")]
impl LlmGatewayGenerator {
    /// Create a new LLM Gateway generator.
    ///
    /// # Arguments
    /// * `router` - Shared ProviderRouter reference (manages providers & health)
    /// * `model_name` - Model name for routing (must match a registered model)
    pub fn new(
        router: std::sync::Arc<crate::llm::router::ProviderRouter>,
        model_name: impl Into<String>,
    ) -> Self {
        Self {
            router,
            model_name: model_name.into(),
            use_failover: true,
        }
    }

    /// Create a generator without automatic failover.
    ///
    /// Uses `route()` instead of `route_with_failover()`, which is faster
    /// but won't retry with alternative providers on failure.
    pub fn without_failover(
        router: std::sync::Arc<crate::llm::router::ProviderRouter>,
        model_name: impl Into<String>,
    ) -> Self {
        Self {
            router,
            model_name: model_name.into(),
            use_failover: false,
        }
    }

    /// Get the model name this generator is configured for.
    pub fn model_name(&self) -> &str {
        &self.model_name
    }

    /// Convert a RAG `ChatMessage` to an LLM Gateway `LlmMessage`.
    fn convert_message(msg: &ChatMessage) -> crate::llm::client::LlmMessage {
        match msg.role {
            super::types::ChatRole::System => {
                crate::llm::client::LlmMessage::system(&msg.content)
            }
            super::types::ChatRole::User => {
                crate::llm::client::LlmMessage::user(&msg.content)
            }
            super::types::ChatRole::Assistant => {
                crate::llm::client::LlmMessage::assistant(&msg.content)
            }
        }
    }

    /// Build a `ChatConfig` from `RagConfig`.
    fn build_chat_config(config: &RagConfig) -> crate::llm::types::ChatConfig {
        crate::llm::types::ChatConfig {
            temperature: Some(config.temperature),
            max_tokens: Some(config.max_generation_tokens),
            stream: false,
            ..Default::default()
        }
    }

    /// Convert a Gateway `ChatResponse` to a RAG `RagResponse`.
    fn convert_response(response: crate::llm::types::ChatResponse) -> RagResponse {
        // Phase-82 T82.3: Use From conversion which now carries cost + reasoning_tokens
        let usage = response.usage.as_ref().map(RagTokenUsage::from);

        let mut metadata = std::collections::HashMap::new();
        metadata.insert(
            "model".to_string(),
            serde_json::json!(response.model),
        );
        metadata.insert(
            "backend".to_string(),
            serde_json::json!("llm_gateway"),
        );
        metadata.insert(
            "finish_reason".to_string(),
            serde_json::json!(format!("{:?}", response.finish_reason)),
        );
        if let Some(ref thinking) = response.thinking {
            metadata.insert(
                "thinking".to_string(),
                serde_json::json!(thinking),
            );
        }
        if let Some(duration_ms) = response.duration_ms {
            metadata.insert(
                "duration_ms".to_string(),
                serde_json::json!(duration_ms),
            );
        }
        // Include cost if available
        if let Some(ref usage_info) = response.usage {
            if let Some(cost) = usage_info.cost {
                metadata.insert("cost".to_string(), serde_json::json!(cost));
            }
        }

        RagResponse {
            answer: response.content,
            sources: Vec::new(), // filled by pipeline
            session_id: None,
            usage,
            retrieval_count: 0,
            latency_ms: response.duration_ms,
            metadata,
        }
    }

    /// Execute generation with failover routing.
    fn generate_with_failover(
        &self,
        llm_messages: Vec<crate::llm::client::LlmMessage>,
        chat_config: crate::llm::types::ChatConfig,
    ) -> RagResult<RagResponse> {
        let response = tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(async {
                self.router
                    .route_with_failover(&self.model_name, |client| {
                        let msgs = llm_messages.clone();
                        let cfg = chat_config.clone();
                        Box::pin(async move { client.chat(&msgs, &cfg).await })
                    })
                    .await
            })
        })
        .map_err(|e| RagError::Generation(format!("LLM Gateway call failed: {e}")))?;

        Ok(Self::convert_response(response))
    }

    /// Execute generation with direct (non-failover) routing.
    fn generate_direct(
        &self,
        llm_messages: Vec<crate::llm::client::LlmMessage>,
        chat_config: crate::llm::types::ChatConfig,
    ) -> RagResult<RagResponse> {
        let (client, _provider_id) = self
            .router
            .route(&self.model_name)
            .map_err(|e| RagError::Generation(format!("LLM routing failed: {e}")))?;

        let response = tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current()
                .block_on(async { client.chat(&llm_messages, &chat_config).await })
        })
        .map_err(|e| RagError::Generation(format!("LLM Gateway call failed: {e}")))?;

        Ok(Self::convert_response(response))
    }
}

#[cfg(feature = "llm-gateway")]
impl Generator for LlmGatewayGenerator {
    fn generate(
        &self,
        messages: &[ChatMessage],
        config: &RagConfig,
    ) -> RagResult<RagResponse> {
        // Convert RAG ChatMessages → LLM Gateway LlmMessages
        let llm_messages: Vec<crate::llm::client::LlmMessage> =
            messages.iter().map(Self::convert_message).collect();

        // Build ChatConfig from RagConfig
        let chat_config = Self::build_chat_config(config);

        tracing::debug!(
            model = %self.model_name,
            message_count = messages.len(),
            failover = self.use_failover,
            "LlmGatewayGenerator: generating response"
        );

        // Route and generate
        if self.use_failover {
            self.generate_with_failover(llm_messages, chat_config)
        } else {
            self.generate_direct(llm_messages, chat_config)
        }
    }
}

// ============================================================================
// LlmGatewayGenerator: StreamGenerator impl (Phase-82: T82.2)
// ============================================================================

#[cfg(feature = "llm-gateway")]
#[async_trait::async_trait]
impl super::traits::StreamGenerator for LlmGatewayGenerator {
    async fn generate_stream(
        &self,
        messages: &[ChatMessage],
        config: &RagConfig,
    ) -> RagResult<
        std::pin::Pin<
            Box<
                dyn futures_core::Stream<Item = RagResult<crate::llm::types::StreamEvent>>
                    + Send,
            >,
        >,
    > {
        // Convert RAG ChatMessages → LLM Gateway LlmMessages
        let llm_messages: Vec<crate::llm::client::LlmMessage> =
            messages.iter().map(Self::convert_message).collect();

        // Build ChatConfig with streaming enabled
        let mut chat_config = Self::build_chat_config(config);
        chat_config.stream = true;

        tracing::debug!(
            model = %self.model_name,
            message_count = messages.len(),
            "LlmGatewayGenerator: starting stream"
        );

        // Route to provider (direct routing for streaming — failover is complex with streams)
        let (client, provider_id) = self
            .router
            .route(&self.model_name)
            .map_err(|e| RagError::Generation(format!("LLM routing failed: {e}")))?;

        tracing::debug!(
            provider = %provider_id,
            model = %self.model_name,
            "LlmGatewayGenerator: stream routed to provider"
        );

        // Get stream from client
        let llm_stream = client
            .chat_stream(&llm_messages, &chat_config)
            .await
            .map_err(|e| {
                RagError::Generation(format!("LLM stream initialization failed: {e}"))
            })?;

        // Map LlmResult<StreamEvent> → RagResult<StreamEvent>
        use tokio_stream::StreamExt;
        let mapped = llm_stream.map(|event| {
            event.map_err(|e| RagError::Generation(format!("Stream event error: {e}")))
        });

        Ok(Box::pin(mapped))
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use super::super::types::{ChatMessage, ChatRole};

    /// A mock reasoning engine for testing
    struct MockReasoningEngine {
        response: String,
    }

    impl crate::perception::ReasoningEngine for MockReasoningEngine {
        fn check_logic_consistency(
            &self,
            _a: &str,
            _b: &str,
        ) -> Result<bool, crate::perception::ReasoningError> {
            Ok(true)
        }

        fn understand_intent(
            &self,
            _input: &str,
        ) -> Result<String, crate::perception::ReasoningError> {
            Ok("intent".to_string())
        }

        fn summarize(
            &self,
            content: &str,
            _max_tokens: usize,
        ) -> Result<String, crate::perception::ReasoningError> {
            Ok(format!("Summary of: {}", &content[..content.len().min(50)]))
        }

        fn chat(
            &self,
            messages: &[crate::perception::Message],
        ) -> Result<crate::perception::ReasoningOutput, crate::perception::ReasoningError> {
            Ok(crate::perception::ReasoningOutput {
                content: format!("{} [from {} messages]", self.response, messages.len()),
                usage: Some(crate::perception::TokenUsage {
                    prompt_tokens: 50,
                    completion_tokens: 20,
                    total_tokens: 70,
                }),
                model: Some("mock-model".to_string()),
            })
        }

        fn model_name(&self) -> &str {
            "mock-reasoning"
        }
    }

    #[test]
    fn test_external_llm_generator() {
        let mock = Box::new(MockReasoningEngine {
            response: "The answer is 42".to_string(),
        });
        let gen = ExternalLlmGenerator::from_reasoning_engine(mock, "mock-model");

        let messages = vec![
            ChatMessage::system("You are helpful."),
            ChatMessage::user("What is the answer?"),
        ];

        let config = RagConfig::default();
        let response = gen.generate(&messages, &config).unwrap();

        assert!(response.answer.contains("The answer is 42"));
        assert!(response.answer.contains("2 messages"));
        assert!(response.usage.is_some());

        let usage = response.usage.unwrap();
        assert_eq!(usage.prompt_tokens, 50);
        assert_eq!(usage.completion_tokens, 20);
        assert_eq!(usage.total_tokens, 70);
    }

    #[test]
    fn test_external_llm_generator_from_shared() {
        let mock = std::sync::Arc::new(MockReasoningEngine {
            response: "Shared answer".to_string(),
        }) as std::sync::Arc<dyn crate::perception::ReasoningEngine>;

        let gen = ExternalLlmGenerator::from_shared_engine(mock, "shared-model");

        let messages = vec![ChatMessage::user("Hello")];
        let config = RagConfig::default();
        let response = gen.generate(&messages, &config).unwrap();

        assert!(response.answer.contains("Shared answer"));
    }

    #[test]
    fn test_generator_trait_object() {
        let mock = Box::new(MockReasoningEngine {
            response: "Trait object test".to_string(),
        });
        let gen: Box<dyn Generator> =
            Box::new(ExternalLlmGenerator::from_reasoning_engine(mock, "test"));

        let messages = vec![ChatMessage::user("Test")];
        let config = RagConfig::default();
        let response = gen.generate(&messages, &config).unwrap();
        assert!(response.answer.contains("Trait object test"));
    }
}
