//! RAG (Retrieval-Augmented Generation) Tools
//!
//! Exposes the RAG system functionality as MCP tools for AI-powered
//! document search, retrieval, and context-aware answer generation.

use std::sync::Arc;

use anyhow::{Context as _, Result};
use provisioning_rag::{
    config::*, DbConnection, EmbeddingEngine, RagAgent, RetrieverEngine, WorkspaceContext,
};
use serde::{Deserialize, Serialize};
use tokio::sync::Mutex;

/// RAG system error
#[derive(Debug)]
#[allow(clippy::enum_variant_names)]
pub enum RagToolError {
    /// Database connection failed
    DatabaseError(String),
    /// RAG agent creation failed
    AgentError(String),
    /// Search query failed
    SearchError(String),
    /// Configuration error
    ConfigError(String),
}

impl std::fmt::Display for RagToolError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RagToolError::DatabaseError(e) => write!(f, "Database error: {}", e),
            RagToolError::AgentError(e) => write!(f, "Agent error: {}", e),
            RagToolError::SearchError(e) => write!(f, "Search error: {}", e),
            RagToolError::ConfigError(e) => write!(f, "Config error: {}", e),
        }
    }
}

impl std::error::Error for RagToolError {}

/// Search result from semantic search
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SearchResultTool {
    pub doc_id: String,
    pub source_path: String,
    pub doc_type: String,
    pub content: String,
    pub similarity: f32,
}

/// RAG Agent Tool - Answer questions using semantic search and Claude
#[derive(Clone)]
pub struct RagAgentTool {
    agent: Arc<Mutex<Option<RagAgent>>>,
    initialized: Arc<Mutex<bool>>,
}

impl RagAgentTool {
    /// Create a new RAG agent tool
    pub fn new() -> Self {
        Self {
            agent: Arc::new(Mutex::new(None)),
            initialized: Arc::new(Mutex::new(false)),
        }
    }

    /// Initialize the RAG agent with database and retriever
    pub async fn initialize(&self) -> Result<()> {
        let mut initialized = self.initialized.lock().await;
        if *initialized {
            return Ok(());
        }

        // Setup database
        let db_config = VectorDbConfig::default();
        let db = DbConnection::new(db_config)
            .await
            .context("Failed to create database connection")?;

        db.initialize_schema()
            .await
            .context("Failed to initialize database schema")?;

        // Setup embeddings
        let embedding_config = EmbeddingConfig::default();
        let embedding_engine =
            EmbeddingEngine::new(embedding_config).context("Failed to create embedding engine")?;

        // Setup retriever
        let retrieval_config = RetrievalConfig::default();
        let retriever = RetrieverEngine::new(retrieval_config, db, embedding_engine)
            .await
            .context("Failed to create retriever engine")?;

        // Setup workspace context
        let workspace = WorkspaceContext::new(
            "provisioning-platform".to_string(),
            "/provisioning".to_string(),
        );

        // Create RAG agent
        let agent = RagAgent::new(retriever, workspace, "claude-opus-4-1".to_string())
            .context("Failed to create RAG agent")?;

        let mut agent_guard = self.agent.lock().await;
        *agent_guard = Some(agent);
        *initialized = true;

        tracing::info!("RAG agent tool initialized successfully");
        Ok(())
    }

    /// Ask a question and get an answer
    pub async fn ask(&self, question: &str) -> Result<RagAnswer> {
        // Ensure initialized
        self.initialize().await?;

        let agent_guard = self.agent.lock().await;
        let agent = agent_guard
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("RAG agent not initialized"))?;

        let response = agent
            .ask(question)
            .await
            .context("Failed to generate answer")?;

        Ok(RagAnswer {
            answer: response.answer,
            confidence: response.confidence,
            sources_count: response.sources.len(),
            sources: response
                .sources
                .into_iter()
                .map(|s| SearchResultTool {
                    doc_id: s.doc_id,
                    source_path: s.source_path,
                    doc_type: s.doc_type,
                    content: s.content,
                    similarity: s.similarity,
                })
                .collect(),
        })
    }
}

/// RAG Answer response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RagAnswer {
    pub answer: String,
    pub confidence: f32,
    pub sources_count: usize,
    pub sources: Vec<SearchResultTool>,
}

/// Semantic Search Tool - Find documents by vector similarity
#[derive(Clone)]
pub struct SemanticSearchTool {
    retriever: Arc<Mutex<Option<RetrieverEngine>>>,
    initialized: Arc<Mutex<bool>>,
}

impl SemanticSearchTool {
    /// Create a new semantic search tool
    pub fn new() -> Self {
        Self {
            retriever: Arc::new(Mutex::new(None)),
            initialized: Arc::new(Mutex::new(false)),
        }
    }

    /// Initialize the retriever
    pub async fn initialize(&self) -> Result<()> {
        let mut initialized = self.initialized.lock().await;
        if *initialized {
            return Ok(());
        }

        // Setup database
        let db_config = VectorDbConfig::default();
        let db = DbConnection::new(db_config)
            .await
            .context("Failed to create database connection")?;

        db.initialize_schema()
            .await
            .context("Failed to initialize database schema")?;

        // Setup embeddings
        let embedding_config = EmbeddingConfig::default();
        let embedding_engine =
            EmbeddingEngine::new(embedding_config).context("Failed to create embedding engine")?;

        // Setup retriever
        let retrieval_config = RetrievalConfig::default();
        let retriever = RetrieverEngine::new(retrieval_config, db, embedding_engine)
            .await
            .context("Failed to create retriever engine")?;

        let mut retriever_guard = self.retriever.lock().await;
        *retriever_guard = Some(retriever);
        *initialized = true;

        tracing::info!("Semantic search tool initialized successfully");
        Ok(())
    }

    /// Search for documents by query
    pub async fn search(&self, query: &str, top_k: Option<usize>) -> Result<Vec<SearchResultTool>> {
        // Ensure initialized
        self.initialize().await?;

        let retriever_guard = self.retriever.lock().await;
        let retriever = retriever_guard
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Retriever not initialized"))?;

        let results = retriever
            .search(query, top_k)
            .await
            .context("Search failed")?;

        Ok(results
            .into_iter()
            .map(|r| SearchResultTool {
                doc_id: r.doc_id,
                source_path: r.source_path,
                doc_type: r.doc_type,
                content: r.content,
                similarity: r.similarity,
            })
            .collect())
    }
}

/// Get RAG System Status
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RagStatus {
    pub operational: bool,
    pub database_ready: bool,
    pub embedding_engine_ready: bool,
    pub agent_ready: bool,
    pub version: String,
    pub features: Vec<String>,
}

/// Get RAG system status
pub async fn get_rag_status() -> Result<RagStatus> {
    // Check if components can be initialized
    let db_config = VectorDbConfig::default();
    let db_result = DbConnection::new(db_config).await;

    let embedding_result = EmbeddingEngine::new(EmbeddingConfig::default());

    let operational = db_result.is_ok() && embedding_result.is_ok();

    Ok(RagStatus {
        operational,
        database_ready: db_result.is_ok(),
        embedding_engine_ready: embedding_result.is_ok(),
        agent_ready: operational,
        version: "0.1.0".to_string(),
        features: vec![
            "semantic_search".to_string(),
            "document_retrieval".to_string(),
            "context_aware_qa".to_string(),
            "claude_integration".to_string(),
            "workspace_context".to_string(),
        ],
    })
}

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

    #[tokio::test]
    async fn test_rag_status() {
        let status = get_rag_status().await;
        assert!(status.is_ok());
        let status = status.unwrap();
        assert!(!status.features.is_empty());
    }

    #[test]
    fn test_semantic_search_tool_creation() {
        let _tool = SemanticSearchTool::new();
        // Just test instantiation
    }

    #[test]
    fn test_rag_agent_tool_creation() {
        let _tool = RagAgentTool::new();
        // Just test instantiation
    }
}
