//! In-memory storage backend

use std::sync::Arc;

use async_trait::async_trait;
use dashmap::DashMap;

use crate::error::{KbError, Result};
use crate::models::{Graph, Node};
use crate::storage::Storage;

/// In-memory storage backend using `DashMap`
///
/// Stores graphs in a concurrent hash map for fast access.
/// Thread-safe and ideal for testing or caching scenarios.
pub struct MemoryStorage {
    graphs: Arc<DashMap<String, Graph>>,
}

impl MemoryStorage {
    /// Create a new empty in-memory storage
    #[must_use]
    pub fn new() -> Self {
        Self {
            graphs: Arc::new(DashMap::new()),
        }
    }
}

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

#[async_trait]
impl Storage for MemoryStorage {
    async fn save_graph(&mut self, graph: &Graph) -> Result<()> {
        self.graphs.insert(graph.name.clone(), graph.clone());
        Ok(())
    }

    async fn load_graph(&self, name: &str) -> Result<Graph> {
        self.graphs
            .get(name)
            .map(|g| g.clone())
            .ok_or_else(|| KbError::Graph(format!("Graph not found: {name}")))
    }

    async fn save_node(&mut self, node: &Node) -> Result<()> {
        let graph_name = node
            .project
            .clone()
            .unwrap_or_else(|| "default".to_string());

        let mut graph = self
            .load_graph(&graph_name)
            .await
            .unwrap_or_else(|_| Graph::new(graph_name.clone()));

        graph.nodes.insert(node.id.clone(), node.clone());
        self.save_graph(&graph).await
    }

    async fn load_node(&self, graph_name: &str, node_id: &str) -> Result<Node> {
        self.graphs
            .get(graph_name)
            .and_then(|graph| graph.nodes.get(node_id).cloned())
            .ok_or_else(|| KbError::NodeNotFound(format!("{graph_name}/{node_id}")))
    }

    async fn delete_node(&mut self, graph_name: &str, node_id: &str) -> Result<()> {
        if let Some(mut graph) = self.graphs.get_mut(graph_name) {
            if graph.nodes.remove(node_id).is_some() {
                return Ok(());
            }
        }
        Err(KbError::NodeNotFound(format!("{graph_name}/{node_id}")))
    }

    async fn list_graphs(&self) -> Result<Vec<String>> {
        Ok(self
            .graphs
            .iter()
            .map(|entry| entry.key().clone())
            .collect())
    }

    async fn list_nodes(&self, graph_name: &str, node_type: Option<&str>) -> Result<Vec<Node>> {
        let graph = self
            .graphs
            .get(graph_name)
            .ok_or_else(|| KbError::Graph(format!("Graph not found: {graph_name}")))?;

        let mut nodes: Vec<Node> = graph.nodes.values().cloned().collect();

        if let Some(filter_type) = node_type {
            nodes.retain(|n| n.node_type.to_string() == filter_type);
        }

        Ok(nodes)
    }
}
