// Copyright 2026 Gregorio Momm
// Licensed under the Apache License, Version 2.0
//
// This file implements the GraphBackend trait for NetworKitRust, adding Cypher
// property graph semantics on top of the NetworKit-inspired core graph structure.

//! GraphBackend implementation wrapping the NetworKit-inspired graph.
//!
//! This layer adds Cypher property graph features (labels, properties, types)
//! on top of the high-performance NetworKit core.

use std::collections::HashSet;
use rustc_hash::FxHashMap;
use indexmap::IndexMap;

use crate::graph::backend::{GraphBackend, NodeLike, EdgeLike};
use crate::graph::types::{GraphEdge, GraphNode, Path, PropertyValue};
use crate::error::{ExecutionError, ExecutionResult};

use super::graph::{Graph, GraphConfig, NodeId as NKNodeId};
use super::algorithms::distance::BFS;

/// NetworKit-based graph backend with Cypher property graph features.
///
/// Architecture:
/// - Core: NetworKit-inspired adjacency list (high performance)
/// - Layer: Property/label storage (Cypher compatibility)
/// - Integration: GraphBackend trait implementation
#[derive(Debug)]
pub struct NetworKitRustBackend {
    /// Core graph structure (NetworKit-inspired).
    pub(crate) graph: Graph,

    /// Node labels (Cypher feature).
    node_labels: FxHashMap<u64, HashSet<String>>,

    /// Node properties (Cypher feature).
    pub(crate) node_properties: FxHashMap<u64, IndexMap<String, PropertyValue>>,

    /// Edge relationship types (Cypher feature).
    edge_types: FxHashMap<u64, String>,

    /// Edge properties (Cypher feature).
    edge_properties: FxHashMap<u64, IndexMap<String, PropertyValue>>,

    /// Reverse mapping: NetworKit edge ID → (source, target).
    edge_endpoints: FxHashMap<u64, (u64, u64)>,

    /// Label index: label → set of node IDs.
    label_index: FxHashMap<String, HashSet<u64>>,

    /// Type index: relationship type → set of edge IDs.
    type_index: FxHashMap<String, HashSet<u64>>,

    /// Next stable node ID (separate from NetworKit's internal IDs).
    next_node_id: u64,

    /// Next stable edge ID.
    next_edge_id: u64,

    /// Mapping: stable node ID → NetworKit node ID.
    pub(crate) node_id_map: FxHashMap<u64, NKNodeId>,

    /// Reverse mapping: NetworKit node ID → stable node ID.
    pub(crate) nk_to_stable: FxHashMap<NKNodeId, u64>,

    /// Mutable access cache: assembled GraphNode objects.
    node_cache: FxHashMap<u64, GraphNode>,

    /// Mutable access cache: assembled GraphEdge objects.
    edge_cache: FxHashMap<u64, GraphEdge>,

    /// Dirty nodes that need to be synced back to storage.
    dirty_nodes: HashSet<u64>,

    /// Dirty edges that need to be synced back to storage.
    dirty_edges: HashSet<u64>,
}

impl NetworKitRustBackend {
    /// Create a new NetworKit-based backend with default configuration.
    ///
    /// NOTE: Always uses a DIRECTED graph configuration because OpenCypher relationships
    /// are directed. Undirected patterns `(a)-[:R]-(b)` are handled in the pattern matcher
    /// by following edges in both directions, not by storing bidirectional edges.
    pub fn new() -> Self {
        Self::with_config(GraphConfig::directed().with_edge_index())
    }

    /// Create a new backend with custom graph configuration.
    pub fn with_config(config: GraphConfig) -> Self {
        Self {
            graph: Graph::new(config),
            node_labels: FxHashMap::default(),
            node_properties: FxHashMap::default(),
            edge_types: FxHashMap::default(),
            edge_properties: FxHashMap::default(),
            edge_endpoints: FxHashMap::default(),
            label_index: FxHashMap::default(),
            type_index: FxHashMap::default(),
            next_node_id: 1,
            next_edge_id: 0,  // CRITICAL FIX: Must match Graph's next_edge_id initial value (0)
            node_id_map: FxHashMap::default(),
            nk_to_stable: FxHashMap::default(),
            node_cache: FxHashMap::default(),
            edge_cache: FxHashMap::default(),
            dirty_nodes: HashSet::default(),
            dirty_edges: HashSet::default(),
        }
    }

    /// Create a backend optimized for large graphs.
    ///
    /// NOTE: Always uses a DIRECTED graph configuration because Cypher relationships
    /// are directed. Undirected patterns are handled in the pattern matcher.
    pub fn with_capacity(nodes: usize, avg_degree: usize) -> Self {
        let config = GraphConfig::directed()
            .with_capacity(nodes, avg_degree)
            .with_edge_index();

        let mut backend = Self::with_config(config);

        // Preallocate metadata storage
        backend.node_labels.reserve(nodes);
        backend.node_properties.reserve(nodes);
        backend.node_id_map.reserve(nodes);
        backend.nk_to_stable.reserve(nodes);

        let estimated_edges = nodes * avg_degree / 2;
        backend.edge_types.reserve(estimated_edges);
        backend.edge_properties.reserve(estimated_edges);
        backend.edge_endpoints.reserve(estimated_edges);

        backend
    }

    /// Helper: Allocate a stable node ID and create mapping.
    fn allocate_node_id(&mut self) -> (u64, NKNodeId) {
        let stable_id = self.next_node_id;
        self.next_node_id += 1;

        let nk_id = self.graph.add_node();

        self.node_id_map.insert(stable_id, nk_id);
        self.nk_to_stable.insert(nk_id, stable_id);

        (stable_id, nk_id)
    }

    /// Helper: Get NetworKit node ID from stable ID.
    fn get_nk_node(&self, stable_id: u64) -> Option<NKNodeId> {
        self.node_id_map.get(&stable_id).copied()
    }

    /// Helper: Get stable node ID from NetworKit ID.
    fn get_stable_node(&self, nk_id: NKNodeId) -> Option<u64> {
        self.nk_to_stable.get(&nk_id).copied()
    }

    /// Sync cached nodes and edges back to separate storage.
    fn sync_cache(&mut self) {
        // Sync dirty nodes
        for node_id in self.dirty_nodes.drain() {
            if let Some(node) = self.node_cache.get(&node_id) {
                // Update labels - need to handle label index changes
                let old_labels = self.node_labels.get(&node_id);
                let new_labels = &node.labels;

                // Remove from old label indices
                if let Some(old) = old_labels {
                    for label in old {
                        if !new_labels.contains(label) {
                            if let Some(set) = self.label_index.get_mut(label) {
                                set.remove(&node_id);
                            }
                        }
                    }
                }

                // Add to new label indices
                for label in new_labels {
                    if old_labels.map(|old| !old.contains(label)).unwrap_or(true) {
                        self.label_index
                            .entry(label.clone())
                            .or_default()
                            .insert(node_id);
                    }
                }

                // Update stored labels and properties
                self.node_labels.insert(node_id, node.labels.clone());
                self.node_properties.insert(node_id, node.properties.clone());
            }
        }

        // Sync dirty edges
        for edge_id in self.dirty_edges.drain() {
            if let Some(edge) = self.edge_cache.get(&edge_id) {
                // Update type - need to handle type index changes
                let old_type = self.edge_types.get(&edge_id);
                let new_type = &edge.rel_type;

                // Remove from old type index
                if let Some(old) = old_type {
                    if old != new_type {
                        if let Some(set) = self.type_index.get_mut(old) {
                            set.remove(&edge_id);
                        }
                    }
                }

                // Add to new type index
                if old_type.map(|old| old != new_type).unwrap_or(true) {
                    self.type_index
                        .entry(new_type.clone())
                        .or_default()
                        .insert(edge_id);
                }

                // Update stored type and properties
                self.edge_types.insert(edge_id, edge.rel_type.clone());
                self.edge_properties.insert(edge_id, edge.properties.clone());
            }
        }
    }

    /// Compact memory usage (useful after bulk operations).
    pub fn shrink_to_fit(&mut self) {
        self.graph.shrink_to_fit();
        self.node_labels.shrink_to_fit();
        self.node_properties.shrink_to_fit();
        self.edge_types.shrink_to_fit();
        self.edge_properties.shrink_to_fit();
        self.edge_endpoints.shrink_to_fit();
        self.label_index.shrink_to_fit();
        self.type_index.shrink_to_fit();
        self.node_id_map.shrink_to_fit();
        self.nk_to_stable.shrink_to_fit();
        self.node_cache.shrink_to_fit();
        self.edge_cache.shrink_to_fit();
    }
}

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

/// Node reference for NetworKitRustBackend.
pub struct NKNodeRef<'a> {
    id: u64,
    labels: &'a HashSet<String>,
    properties: &'a IndexMap<String, PropertyValue>,
}

impl<'a> NodeLike for NKNodeRef<'a> {
    fn id(&self) -> u64 {
        self.id
    }

    fn labels(&self) -> &HashSet<String> {
        self.labels
    }

    fn properties(&self) -> &IndexMap<String, PropertyValue> {
        self.properties
    }
}

/// Edge reference for NetworKitRustBackend.
pub struct NKEdgeRef<'a> {
    id: u64,
    rel_type: &'a str,
    properties: &'a IndexMap<String, PropertyValue>,
}

impl<'a> EdgeLike for NKEdgeRef<'a> {
    fn id(&self) -> u64 {
        self.id
    }

    fn rel_type(&self) -> &str {
        self.rel_type
    }

    fn properties(&self) -> &IndexMap<String, PropertyValue> {
        self.properties
    }
}

impl GraphBackend for NetworKitRustBackend {
    type NodeRef<'a> = NKNodeRef<'a>;
    type EdgeRef<'a> = NKEdgeRef<'a>;
    type NodesIter<'a> = Box<dyn Iterator<Item = NKNodeRef<'a>> + 'a>;
    type EdgesIter<'a> = Box<dyn Iterator<Item = NKEdgeRef<'a>> + 'a>;

    fn get_node(&self, id: u64) -> Option<Self::NodeRef<'_>> {
        if !self.node_id_map.contains_key(&id) {
            return None;
        }

        // CRITICAL FIX: Check cache first to avoid returning stale data
        // If node is in cache (possibly dirty), use cached version
        if let Some(cached_node) = self.node_cache.get(&id) {
            return Some(NKNodeRef {
                id,
                labels: &cached_node.labels,
                properties: &cached_node.properties,
            });
        }

        // Otherwise, read from separate storage
        let labels = self.node_labels.get(&id);
        let properties = self.node_properties.get(&id);

        // For now, require both to exist (simplifies lifetime issues)
        if labels.is_none() || properties.is_none() {
            // Actually, let's handle this properly by ensuring they always exist
            return None;
        }

        Some(NKNodeRef {
            id,
            labels: labels.unwrap(),
            properties: properties.unwrap(),
        })
    }

    fn get_node_mut(&mut self, id: u64) -> Option<&mut GraphNode> {
        // Check if node exists in the graph
        if !self.node_id_map.contains_key(&id) {
            return None;
        }

        // If not in cache, assemble GraphNode from separate storage
        if !self.node_cache.contains_key(&id) {
            let labels = self.node_labels.get(&id)?.clone();
            let properties = self.node_properties.get(&id)?.clone();

            let mut node = GraphNode::new(id);
            node.labels = labels;
            node.properties = properties;

            self.node_cache.insert(id, node);
        }

        // Mark as dirty
        self.dirty_nodes.insert(id);

        // Return mutable reference to cached node
        self.node_cache.get_mut(&id)
    }

    fn all_nodes(&self) -> Self::NodesIter<'_> {
        // Note: If there are dirty nodes in cache, they won't be visible here
        // since we can't sync with immutable borrow. This is acceptable because
        // reads should happen in a separate phase from writes in query execution.
        Box::new(self.node_id_map.keys().copied().filter_map(move |id| self.get_node(id)))
    }

    fn nodes_with_label(&self, label: &str) -> Vec<Self::NodeRef<'_>> {
        self.label_index
            .get(label)
            .map(|node_ids| {
                node_ids
                    .iter()
                    .filter_map(|&id| self.get_node(id))
                    .collect()
            })
            .unwrap_or_default()
    }

    fn get_edge(&self, id: u64) -> Option<Self::EdgeRef<'_>> {
        // CRITICAL FIX: Check cache first to avoid returning stale data
        // If edge is in cache (possibly dirty), use cached version
        if let Some(cached_edge) = self.edge_cache.get(&id) {
            return Some(NKEdgeRef {
                id,
                rel_type: &cached_edge.rel_type,
                properties: &cached_edge.properties,
            });
        }

        // Otherwise, read from separate storage
        let rel_type = self.edge_types.get(&id)?;
        let properties = self.edge_properties.get(&id)?;

        Some(NKEdgeRef {
            id,
            rel_type: rel_type.as_str(),
            properties,
        })
    }

    fn get_edge_mut(&mut self, id: u64) -> Option<&mut GraphEdge> {
        // Check if edge exists
        if !self.edge_types.contains_key(&id) {
            return None;
        }

        // If not in cache, assemble GraphEdge from separate storage
        if !self.edge_cache.contains_key(&id) {
            let rel_type = self.edge_types.get(&id)?.clone();
            let properties = self.edge_properties.get(&id)?.clone();

            let mut edge = GraphEdge::new(id, rel_type);
            edge.properties = properties;

            self.edge_cache.insert(id, edge);
        }

        // Mark as dirty
        self.dirty_edges.insert(id);

        // Return mutable reference to cached edge
        self.edge_cache.get_mut(&id)
    }

    fn all_edges(&self) -> Self::EdgesIter<'_> {
        Box::new(self.edge_types.keys().copied().filter_map(move |id| self.get_edge(id)))
    }

    fn edges_with_type(&self, rel_type: &str) -> Vec<Self::EdgeRef<'_>> {
        self.type_index
            .get(rel_type)
            .map(|edge_ids| {
                edge_ids
                    .iter()
                    .filter_map(|&id| self.get_edge(id))
                    .collect()
            })
            .unwrap_or_default()
    }

    fn get_edge_endpoints(&self, edge_id: u64) -> Option<(u64, u64)> {
        self.edge_endpoints.get(&edge_id).copied()
    }

    fn outgoing_edges(&self, node_id: u64) -> Vec<(u64, Self::EdgeRef<'_>)> {
        let nk_id = match self.get_nk_node(node_id) {
            Some(id) => id,
            None => return Vec::new(),
        };

        self.graph
            .out_neighbors(nk_id)
            .iter()
            .filter_map(|neighbor| {
                let target_stable = self.get_stable_node(neighbor.target)?;
                let edge_id = neighbor.edge_id?;
                let edge_ref = self.get_edge(edge_id)?;
                Some((target_stable, edge_ref))
            })
            .collect()
    }

    fn incoming_edges(&self, node_id: u64) -> Vec<(u64, Self::EdgeRef<'_>)> {
        let nk_id = match self.get_nk_node(node_id) {
            Some(id) => id,
            None => return Vec::new(),
        };

        self.graph
            .in_neighbors(nk_id)
            .iter()
            .filter_map(|neighbor| {
                let source_stable = self.get_stable_node(neighbor.target)?;
                let edge_id = neighbor.edge_id?;
                let edge_ref = self.get_edge(edge_id)?;
                Some((source_stable, edge_ref))
            })
            .collect()
    }

    fn all_edges_for_node(&self, node_id: u64) -> Vec<(u64, u64, Self::EdgeRef<'_>)> {
        let mut edges = Vec::new();

        for (target, edge_ref) in self.outgoing_edges(node_id) {
            edges.push((node_id, target, edge_ref));
        }

        for (source, edge_ref) in self.incoming_edges(node_id) {
            edges.push((source, node_id, edge_ref));
        }

        edges
    }

    fn count_node_relationships(&self, node_id: u64) -> usize {
        let nk_id = match self.get_nk_node(node_id) {
            Some(id) => id,
            None => return 0,
        };

        self.graph.degree(nk_id) + self.graph.in_degree(nk_id)
    }

    fn shortest_path(&self, start_id: u64, end_id: u64) -> Option<Path> {
        let start_nk = self.get_nk_node(start_id)?;
        let end_nk = self.get_nk_node(end_id)?;

        // Run BFS from start to end
        let result = BFS::new(&self.graph, start_nk)
            .with_target(end_nk)
            .run();

        if !result.found {
            return None;
        }

        // Reconstruct path using stable IDs
        let nk_path = result.path(end_nk)?;

        // Convert to stable IDs and build Path
        let stable_ids: Vec<u64> = nk_path
            .iter()
            .filter_map(|&nk_id| self.get_stable_node(nk_id))
            .collect();

        if stable_ids.is_empty() {
            return None;
        }

        // Build Path object
        let first_node = self.get_node(stable_ids[0])?;
        let mut path = Path::new(GraphNode {
            id: first_node.id(),
            labels: first_node.labels().clone(),
            properties: first_node.properties().clone(),
        });

        for i in 0..stable_ids.len() - 1 {
            let source = stable_ids[i];
            let target = stable_ids[i + 1];

            // Find the edge between these nodes
            let edges = self.outgoing_edges(source);
            if let Some((_, edge_ref)) = edges.iter().find(|(t, _)| *t == target) {
                let edge = GraphEdge {
                    id: edge_ref.id(),
                    rel_type: edge_ref.rel_type().to_string(),
                    properties: edge_ref.properties().clone(),
                };

                let target_node = self.get_node(target)?;
                let node = GraphNode {
                    id: target_node.id(),
                    labels: target_node.labels().clone(),
                    properties: target_node.properties().clone(),
                };

                path.extend(edge, node);
            }
        }

        Some(path)
    }

    fn all_shortest_paths(&self, start_id: u64, end_id: u64) -> Vec<Path> {
        // For now, return single shortest path
        // Full implementation would need modified BFS to track all paths
        self.shortest_path(start_id, end_id).into_iter().collect()
    }

    fn add_existing_node(&mut self, node: GraphNode) {
        self.sync_cache();
        let stable_id = node.id;

        // Add to NetworKit graph and build bidirectional ID maps
        let nk_id = self.graph.add_node();
        self.node_id_map.insert(stable_id, nk_id);
        self.nk_to_stable.insert(nk_id, stable_id);

        // Advance counter to avoid future collisions
        if stable_id >= self.next_node_id {
            self.next_node_id = stable_id + 1;
        }

        // Store labels and update label index
        for label in &node.labels {
            self.label_index
                .entry(label.clone())
                .or_default()
                .insert(stable_id);
        }
        self.node_labels.insert(stable_id, node.labels);
        self.node_properties.insert(stable_id, node.properties);
    }

    fn create_relationship_with_id(
        &mut self,
        edge_id: u64,
        start_id: u64,
        end_id: u64,
        rel_type: impl Into<String>,
        properties: IndexMap<String, PropertyValue>,
    ) -> ExecutionResult<u64> {
        self.sync_cache();
        let start_nk = self.get_nk_node(start_id)
            .ok_or(ExecutionError::NodeNotFound(start_id))?;
        let end_nk = self.get_nk_node(end_id)
            .ok_or(ExecutionError::NodeNotFound(end_id))?;

        // Advance edge counter to avoid future collisions
        if edge_id >= self.next_edge_id {
            self.next_edge_id = edge_id + 1;
        }

        self.graph.add_edge(start_nk, end_nk, None);

        let rel_type_str = rel_type.into();
        self.type_index
            .entry(rel_type_str.clone())
            .or_default()
            .insert(edge_id);
        self.edge_types.insert(edge_id, rel_type_str);
        self.edge_properties.insert(edge_id, properties);
        self.edge_endpoints.insert(edge_id, (start_id, end_id));

        Ok(edge_id)
    }

    fn create_node(
        &mut self,
        labels: impl IntoIterator<Item = impl Into<String>>,
        properties: IndexMap<String, PropertyValue>,
    ) -> u64 {
        // Sync cache before creating new node
        self.sync_cache();

        let (stable_id, _nk_id) = self.allocate_node_id();

        // Store labels
        let label_set: HashSet<String> = labels.into_iter().map(|l| l.into()).collect();
        for label in &label_set {
            self.label_index
                .entry(label.clone())
                .or_default()
                .insert(stable_id);
        }
        self.node_labels.insert(stable_id, label_set);

        // Store properties
        self.node_properties.insert(stable_id, properties);

        stable_id
    }

    fn create_relationship(
        &mut self,
        start_id: u64,
        end_id: u64,
        rel_type: impl Into<String>,
        properties: IndexMap<String, PropertyValue>,
    ) -> ExecutionResult<u64> {
        // Sync cache before creating new relationship
        self.sync_cache();

        let start_nk = self
            .get_nk_node(start_id)
            .ok_or(ExecutionError::NodeNotFound(start_id))?;
        let end_nk = self
            .get_nk_node(end_id)
            .ok_or(ExecutionError::NodeNotFound(end_id))?;

        let edge_id = self.next_edge_id;
        self.next_edge_id += 1;

        // Add edge to NetworKit graph
        self.graph.add_edge(start_nk, end_nk, None); // Weight can be added later if needed

        // Store relationship type and properties
        let rel_type_str = rel_type.into();
        self.type_index
            .entry(rel_type_str.clone())
            .or_default()
            .insert(edge_id);
        self.edge_types.insert(edge_id, rel_type_str);
        self.edge_properties.insert(edge_id, properties);
        self.edge_endpoints.insert(edge_id, (start_id, end_id));

        Ok(edge_id)
    }

    fn delete_node(&mut self, id: u64) -> ExecutionResult<GraphNode> {
        // Sync cache before deletion
        self.sync_cache();

        let nk_id = self
            .get_nk_node(id)
            .ok_or(ExecutionError::NodeNotFound(id))?;

        // Check for relationships
        if self.graph.degree(nk_id) > 0 || self.graph.in_degree(nk_id) > 0 {
            return Err(ExecutionError::DeleteError(format!(
                "Cannot delete node {} because it still has relationships. Use DETACH DELETE.",
                id
            )));
        }

        // Remove from indices
        if let Some(labels) = self.node_labels.get(&id) {
            for label in labels {
                if let Some(set) = self.label_index.get_mut(label) {
                    set.remove(&id);
                }
            }
        }

        // Get node data before deletion
        let labels = self.node_labels.remove(&id).unwrap_or_default();
        let properties = self.node_properties.remove(&id).unwrap_or_default();

        // Remove from cache if present
        self.node_cache.remove(&id);
        self.dirty_nodes.remove(&id);

        // Remove from graph
        self.graph.remove_node(nk_id);
        self.node_id_map.remove(&id);
        self.nk_to_stable.remove(&nk_id);

        // Construct GraphNode for return
        let mut node = GraphNode::new(id);
        node.labels = labels;
        node.properties = properties;

        Ok(node)
    }

    fn delete_relationship(&mut self, id: u64) -> ExecutionResult<GraphEdge> {
        // Sync cache before deletion
        self.sync_cache();

        let (start_id, end_id) = self
            .edge_endpoints
            .get(&id)
            .copied()
            .ok_or(ExecutionError::RelationshipNotFound(id))?;

        let start_nk = self.get_nk_node(start_id).unwrap();
        let end_nk = self.get_nk_node(end_id).unwrap();

        // Remove from NetworKit graph
        self.graph.remove_edge(start_nk, end_nk);

        // Remove metadata
        let rel_type = self.edge_types.remove(&id).unwrap_or_default();
        let properties = self.edge_properties.remove(&id).unwrap_or_default();
        self.edge_endpoints.remove(&id);

        // Remove from cache if present
        self.edge_cache.remove(&id);
        self.dirty_edges.remove(&id);

        if let Some(set) = self.type_index.get_mut(&rel_type) {
            set.remove(&id);
        }

        // Construct GraphEdge for return
        let mut edge = GraphEdge::new(id, rel_type);
        edge.properties = properties;

        Ok(edge)
    }

    fn detach_delete_node(&mut self, id: u64) -> ExecutionResult<GraphNode> {
        // Sync cache before deletion
        self.sync_cache();

        // Check node exists
        let _nk_id = self
            .get_nk_node(id)
            .ok_or(ExecutionError::NodeNotFound(id))?;

        // Collect all edge IDs to delete (both outgoing and incoming)
        let mut edges_to_delete = Vec::new();

        // Collect outgoing edges
        for (_, edge_ref) in self.outgoing_edges(id) {
            edges_to_delete.push(edge_ref.id());
        }

        // Collect incoming edges
        for (_, edge_ref) in self.incoming_edges(id) {
            edges_to_delete.push(edge_ref.id());
        }

        // Delete all edges first
        for edge_id in edges_to_delete {
            self.delete_relationship(edge_id)?;
        }

        // Now delete the node (which should have no edges)
        self.delete_node(id)
    }

    fn set_node_property(
        &mut self,
        node_id: u64,
        key: impl Into<String>,
        value: PropertyValue,
    ) -> ExecutionResult<()> {
        // Sync dirty cache entries first
        self.sync_cache();

        if !self.node_id_map.contains_key(&node_id) {
            return Err(ExecutionError::NodeNotFound(node_id));
        }

        let key = key.into();
        // Write to authoritative storage
        self.node_properties
            .entry(node_id)
            .or_default()
            .insert(key.clone(), value.clone());

        // Keep cache consistent — update in-place if cached
        if let Some(cached) = self.node_cache.get_mut(&node_id) {
            cached.properties.insert(key, value);
        }

        Ok(())
    }

    fn set_edge_property(
        &mut self,
        edge_id: u64,
        key: impl Into<String>,
        value: PropertyValue,
    ) -> ExecutionResult<()> {
        // Sync dirty cache entries first
        self.sync_cache();

        if !self.edge_types.contains_key(&edge_id) {
            return Err(ExecutionError::RelationshipNotFound(edge_id));
        }

        let key = key.into();
        // Write to authoritative storage
        self.edge_properties
            .entry(edge_id)
            .or_default()
            .insert(key.clone(), value.clone());

        // Keep cache consistent — update in-place if cached
        if let Some(cached) = self.edge_cache.get_mut(&edge_id) {
            cached.properties.insert(key, value);
        }

        Ok(())
    }

    fn remove_node_property(
        &mut self,
        node_id: u64,
        key: &str,
    ) -> ExecutionResult<Option<PropertyValue>> {
        // Sync cache first to ensure we're operating on latest data
        self.sync_cache();

        if !self.node_id_map.contains_key(&node_id) {
            return Err(ExecutionError::NodeNotFound(node_id));
        }

        Ok(self
            .node_properties
            .get_mut(&node_id)
            .and_then(|props| props.shift_remove(key)))
    }

    fn remove_edge_property(
        &mut self,
        edge_id: u64,
        key: &str,
    ) -> ExecutionResult<Option<PropertyValue>> {
        // Sync cache first to ensure we're operating on latest data
        self.sync_cache();

        if !self.edge_types.contains_key(&edge_id) {
            return Err(ExecutionError::RelationshipNotFound(edge_id));
        }

        Ok(self
            .edge_properties
            .get_mut(&edge_id)
            .and_then(|props| props.shift_remove(key)))
    }

    fn add_label(&mut self, node_id: u64, label: impl Into<String>) -> ExecutionResult<()> {
        // Sync cache first to ensure we're operating on latest data
        self.sync_cache();

        if !self.node_id_map.contains_key(&node_id) {
            return Err(ExecutionError::NodeNotFound(node_id));
        }

        let label = label.into();
        self.label_index
            .entry(label.clone())
            .or_default()
            .insert(node_id);
        self.node_labels
            .entry(node_id)
            .or_default()
            .insert(label);

        Ok(())
    }

    fn remove_label(&mut self, node_id: u64, label: &str) -> ExecutionResult<bool> {
        // Sync cache first to ensure we're operating on latest data
        self.sync_cache();

        if !self.node_id_map.contains_key(&node_id) {
            return Err(ExecutionError::NodeNotFound(node_id));
        }

        let removed = self
            .node_labels
            .get_mut(&node_id)
            .map(|labels| labels.remove(label))
            .unwrap_or(false);

        if removed {
            if let Some(set) = self.label_index.get_mut(label) {
                set.remove(&node_id);
            }
        }

        Ok(removed)
    }

    fn node_count(&self) -> usize {
        self.graph.node_count()
    }

    fn edge_count(&self) -> usize {
        self.graph.edge_count()
    }
}

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

    #[test]
    fn test_single_direction_edge_storage() {
        let mut backend = NetworKitRustBackend::new();

        // Create two nodes
        let n1 = backend.create_node(vec!["Person"], IndexMap::new());
        let n2 = backend.create_node(vec!["Person"], IndexMap::new());

        // Create a relationship
        let r1 = backend.create_relationship(n1, n2, "KNOWS", IndexMap::new()).unwrap();

        // Verify edge count is 1 (not 2)
        assert_eq!(backend.edge_count(), 1, "Should have exactly 1 edge");

        // Verify outgoing edges from n1
        let outgoing = backend.outgoing_edges(n1);
        assert_eq!(outgoing.len(), 1, "Should have 1 outgoing edge from n1");
        assert_eq!(outgoing[0].1.id(), r1, "Outgoing edge should have correct ID");
        assert_eq!(outgoing[0].0, n2, "Outgoing edge should point to n2");

        // Verify incoming edges to n2
        let incoming = backend.incoming_edges(n2);
        assert_eq!(incoming.len(), 1, "Should have 1 incoming edge to n2");
        assert_eq!(incoming[0].1.id(), r1, "Incoming edge should have correct ID");
        assert_eq!(incoming[0].0, n1, "Incoming edge should point from n1");

        // Verify all_edges doesn't duplicate
        let all_edges: Vec<_> = backend.all_edges().collect();
        assert_eq!(all_edges.len(), 1, "all_edges() should return exactly 1 edge");
        assert_eq!(all_edges[0].id(), r1, "Edge should have correct ID");
    }

    #[test]
    fn test_multiple_relationships_between_same_nodes() {
        let mut backend = NetworKitRustBackend::new();

        let n1 = backend.create_node(vec!["Person"], IndexMap::new());
        let n2 = backend.create_node(vec!["Person"], IndexMap::new());

        // Create multiple relationships between same nodes
        let r1 = backend.create_relationship(n1, n2, "KNOWS", IndexMap::new()).unwrap();
        let r2 = backend.create_relationship(n1, n2, "LIKES", IndexMap::new()).unwrap();
        let r3 = backend.create_relationship(n2, n1, "KNOWS", IndexMap::new()).unwrap();

        // Should have 3 distinct relationships
        assert_eq!(backend.edge_count(), 3, "Should have 3 edges");

        let outgoing_n1 = backend.outgoing_edges(n1);
        assert_eq!(outgoing_n1.len(), 2, "n1 should have 2 outgoing edges");

        let outgoing_n2 = backend.outgoing_edges(n2);
        assert_eq!(outgoing_n2.len(), 1, "n2 should have 1 outgoing edge");

        // Verify all edges are unique
        let all_edges: Vec<_> = backend.all_edges().collect();
        assert_eq!(all_edges.len(), 3, "Should have 3 unique edges");

        let edge_ids: std::collections::HashSet<_> = all_edges.iter().map(|e| e.id()).collect();
        assert_eq!(edge_ids.len(), 3, "All edge IDs should be unique");
        assert!(edge_ids.contains(&r1));
        assert!(edge_ids.contains(&r2));
        assert!(edge_ids.contains(&r3));
    }
}
