use std::collections::HashMap;
use std::sync::Arc;

use anyhow::Result;
use tracing::{debug, info, warn};

use crate::db::KuzuGraph;

use super::assimilator::KBAssimilator;
use super::connector::KBConnector;
use super::types::{KBQueryResult, KBServerConfig, KBSourceStatus, KBSyncResult, KnowledgeEntry};

/// Central coordinator for knowledge base operations.
///
/// Analogous to SyncBridge but for knowledge base integrations.
/// Manages connector registration, querying, assimilation, and status.
pub struct KnowledgeManager {
    connector: KBConnector,
    assimilator: KBAssimilator,
    kuzu: Arc<KuzuGraph>,
}

impl KnowledgeManager {
    pub fn new(kuzu: Arc<KuzuGraph>) -> Self {
        info!("KnowledgeManager initialized");
        Self {
            connector: KBConnector::new(),
            assimilator: KBAssimilator::new(Arc::clone(&kuzu)),
            kuzu,
        }
    }

    /// Register a knowledge base connection.
    pub async fn configure_kb(&self, config: KBServerConfig) {
        info!(name = %config.name, source = %config.source, "Configuring KB connection");
        self.connector.register(config).await;
    }

    /// Query a knowledge base without assimilation.
    pub async fn query_kb(
        &self,
        name: &str,
        query: &str,
        max_results: usize,
        collection: Option<&str>,
        collection_pattern: Option<&str>,
    ) -> Result<Vec<KBQueryResult>> {
        let collections = self
            .connector
            .resolve_collections(name, collection, collection_pattern)
            .await?;

        let mut results = Vec::new();
        for col in &collections {
            let entries = self.connector.query(name, query, max_results, col).await?;
            let total = entries.len();

            debug!(source = %name, collection = %col, count = total, "KB query results");

            results.push(KBQueryResult {
                entries,
                source: name.to_string(),
                collection: col.clone(),
                query: query.to_string(),
                total_results: total,
            });
        }

        Ok(results)
    }

    /// Query a knowledge base and assimilate results into the graph.
    pub async fn sync_kb(
        &self,
        name: &str,
        query: &str,
        max_results: usize,
        collection: Option<&str>,
        collection_pattern: Option<&str>,
    ) -> Result<KBSyncResult> {
        let query_results = self
            .query_kb(name, query, max_results, collection, collection_pattern)
            .await?;

        let mut all_entries: Vec<KnowledgeEntry> = Vec::new();
        for qr in &query_results {
            all_entries.extend(qr.entries.clone());
        }

        let sync_result = self.assimilator.assimilate(&all_entries, name).await?;

        info!(
            source = %name,
            entries = sync_result.entries_fetched,
            concepts = sync_result.concepts_extracted,
            nodes = sync_result.graph_nodes_created,
            edges = sync_result.graph_edges_created,
            "KB sync complete"
        );

        Ok(sync_result)
    }

    /// Scatter-gather query across all registered KBs.
    /// Used by SearchOrchestrator for automatic KB blending.
    pub async fn query_all_kbs(
        &self,
        query: &str,
        max_results_per_kb: usize,
    ) -> Vec<KBQueryResult> {
        let sources = self.connector.registered_sources().await;
        let mut all_results = Vec::new();

        for name in &sources {
            match self
                .query_kb(name, query, max_results_per_kb, None, None)
                .await
            {
                Ok(results) => all_results.extend(results),
                Err(e) => {
                    warn!(source = %name, error = %e, "KB query failed, skipping");
                }
            }
        }

        all_results
    }

    /// Assimilate KB query results into the knowledge graph.
    /// Converts query results to KnowledgeEntry vec and delegates to the assimilator.
    pub async fn assimilate_results(&self, results: &[KBQueryResult]) -> Result<KBSyncResult> {
        let mut all_entries: Vec<KnowledgeEntry> = Vec::new();
        let mut source = String::new();
        for qr in results {
            all_entries.extend(qr.entries.clone());
            if source.is_empty() {
                source = qr.source.clone();
            }
        }

        if all_entries.is_empty() {
            return Ok(KBSyncResult {
                entries_fetched: 0,
                concepts_extracted: 0,
                graph_nodes_created: 0,
                graph_edges_created: 0,
                source,
            });
        }

        let sync_result = self.assimilator.assimilate(&all_entries, &source).await?;

        info!(
            entries = sync_result.entries_fetched,
            concepts = sync_result.concepts_extracted,
            nodes = sync_result.graph_nodes_created,
            edges = sync_result.graph_edges_created,
            "KB results assimilated into graph"
        );

        Ok(sync_result)
    }

    /// Push a document to a knowledge base collection.
    pub async fn ingest_to_kb(
        &self,
        name: &str,
        title: &str,
        content: &str,
        metadata: &HashMap<String, String>,
        collection: Option<&str>,
    ) -> Result<serde_json::Value> {
        let col = collection.unwrap_or("default");
        self.connector
            .ingest(name, title, content, metadata, col)
            .await
    }

    /// List available collections for a KB source.
    pub async fn list_collections(&self, name: &str) -> Result<Vec<String>> {
        self.connector.list_collections(name).await
    }

    /// Return status information about registered KBs and graph nodes.
    pub async fn kb_status(&self, source_filter: Option<&str>) -> Result<serde_json::Value> {
        let sources = self.connector.registered_sources().await;
        let filtered: Vec<&String> = if let Some(filter) = source_filter {
            sources.iter().filter(|s| s.as_str() == filter).collect()
        } else {
            sources.iter().collect()
        };

        let knowledge_count = self.kuzu.get_knowledge_entries(source_filter).await?.len();
        let concept_count = self.kuzu.get_concept_count().await?;

        Ok(serde_json::json!({
            "registered_sources": filtered,
            "knowledge_nodes": knowledge_count,
            "concept_nodes": concept_count,
        }))
    }

    /// Check if any KB sources are registered.
    pub async fn has_sources(&self) -> bool {
        !self.connector.registered_sources().await.is_empty()
    }

    /// Return per-source sync status: name, whether synced, and knowledge node count.
    pub async fn source_statuses(&self) -> Vec<KBSourceStatus> {
        let sources = self.connector.registered_sources().await;
        let mut statuses = Vec::new();

        for name in &sources {
            let knowledge_count = self
                .kuzu
                .get_knowledge_entries(Some(name))
                .await
                .map(|e| e.len())
                .unwrap_or(0);

            statuses.push(KBSourceStatus {
                name: name.clone(),
                synced: knowledge_count > 0,
                knowledge_count,
            });
        }

        statuses
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::knowledge::types::{KBServerConfig, KBSource};
    use tempfile::TempDir;

    async fn make_test_manager() -> (KnowledgeManager, TempDir) {
        let tmp_kuzu = TempDir::new().expect("temp dir");
        let kuzu = Arc::new(KuzuGraph::new(tmp_kuzu.path()).expect("kuzu"));
        let manager = KnowledgeManager::new(kuzu);
        (manager, tmp_kuzu)
    }

    #[tokio::test]
    async fn test_configure_kb() {
        let (manager, _k) = make_test_manager().await;
        assert!(!manager.has_sources().await);

        manager
            .configure_kb(KBServerConfig {
                source: KBSource::ChromaDB,
                name: "test-kb".into(),
                command: "echo".into(),
                args: vec![],
                env_vars: vec![],
                collection: Some("default".into()),
                collection_pattern: None,
            })
            .await;

        assert!(manager.has_sources().await);
    }

    #[tokio::test]
    async fn test_kb_status_empty() {
        let (manager, _k) = make_test_manager().await;
        let status = manager.kb_status(None).await.unwrap();

        assert_eq!(status["registered_sources"].as_array().unwrap().len(), 0);
        assert_eq!(status["knowledge_nodes"], 0);
        assert_eq!(status["concept_nodes"], 0);
    }

    #[tokio::test]
    async fn test_kb_status_with_source() {
        let (manager, _k) = make_test_manager().await;
        manager
            .configure_kb(KBServerConfig {
                source: KBSource::ChromaDB,
                name: "chroma-1".into(),
                command: "echo".into(),
                args: vec![],
                env_vars: vec![],
                collection: None,
                collection_pattern: None,
            })
            .await;

        let status = manager.kb_status(None).await.unwrap();
        assert_eq!(status["registered_sources"].as_array().unwrap().len(), 1);
    }

    #[tokio::test]
    async fn test_query_all_kbs_empty() {
        let (manager, _k) = make_test_manager().await;
        let results = manager.query_all_kbs("test query", 5).await;
        assert!(results.is_empty());
    }

    #[tokio::test]
    async fn test_assimilate_results_creates_graph_nodes() {
        let (manager, _k) = make_test_manager().await;
        use crate::knowledge::types::{KBQueryResult, KnowledgeEntry};

        let entries = vec![KnowledgeEntry {
            id: "kb-test-1".into(),
            source: "test-kb".into(),
            collection: "docs".into(),
            title: "Authentication Design".into(),
            content: "All endpoints must use JWT.\n# Security\nConsider rate limiting.".into(),
            metadata: std::collections::HashMap::new(),
            relevance_score: 0.9,
            retrieved_at: 1707300600,
        }];

        let results = vec![KBQueryResult {
            entries,
            source: "test-kb".into(),
            collection: "docs".into(),
            query: "auth".into(),
            total_results: 1,
        }];

        let sync = manager.assimilate_results(&results).await.unwrap();
        assert_eq!(sync.entries_fetched, 1);
        assert!(sync.concepts_extracted >= 2); // "must use JWT" + "# Security" + "Consider..."
        assert!(sync.graph_nodes_created >= 3); // 1 knowledge + concepts
        assert!(sync.graph_edges_created >= 2); // DEFINES_REQUIREMENT edges
    }

    #[tokio::test]
    async fn test_empty_kb_results_no_assimilation() {
        let (manager, _k) = make_test_manager().await;

        let results: Vec<crate::knowledge::types::KBQueryResult> = vec![];
        let sync = manager.assimilate_results(&results).await.unwrap();

        assert_eq!(sync.entries_fetched, 0);
        assert_eq!(sync.concepts_extracted, 0);
        assert_eq!(sync.graph_nodes_created, 0);
        assert_eq!(sync.graph_edges_created, 0);
    }

    #[tokio::test]
    async fn test_assimilation_idempotent() {
        let (manager, _k) = make_test_manager().await;
        use crate::knowledge::types::{KBQueryResult, KnowledgeEntry};

        let entries = vec![KnowledgeEntry {
            id: "kb-idem-1".into(),
            source: "test-kb".into(),
            collection: "docs".into(),
            title: "Testing Guide".into(),
            content: "All tests must pass before merging.".into(),
            metadata: std::collections::HashMap::new(),
            relevance_score: 0.8,
            retrieved_at: 1707300600,
        }];

        let results = vec![KBQueryResult {
            entries: entries.clone(),
            source: "test-kb".into(),
            collection: "docs".into(),
            query: "testing".into(),
            total_results: 1,
        }];

        // Assimilate twice — should not error (MERGE is idempotent)
        let sync1 = manager.assimilate_results(&results).await.unwrap();
        let sync2 = manager.assimilate_results(&results).await.unwrap();

        // Both should succeed with same counts
        assert_eq!(sync1.entries_fetched, sync2.entries_fetched);
        assert_eq!(sync1.concepts_extracted, sync2.concepts_extracted);
    }
}
