//! Graph traversal algorithms.

use super::edge::Edge;
use super::node::{Node, NodeId};
use super::store::{Direction, GraphStore};
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::{HashSet, VecDeque};

/// Query for graph traversal.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraversalQuery {
    /// Maximum depth to traverse.
    pub max_depth: usize,
    /// Edge types to follow (empty = all types).
    pub edge_types: Vec<String>,
    /// Direction to traverse.
    pub direction: TraversalDirection,
    /// Filter for nodes to include.
    pub node_filter: Option<JsonValue>,
    /// Filter for edges to follow.
    pub edge_filter: Option<JsonValue>,
    /// Maximum results to return.
    pub limit: Option<usize>,
    /// Labels to filter nodes by.
    pub labels: Vec<String>,
}

/// Direction for traversal serialization.
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum TraversalDirection {
    Outgoing,
    Incoming,
    Both,
}

impl From<TraversalDirection> for Direction {
    fn from(td: TraversalDirection) -> Self {
        match td {
            TraversalDirection::Outgoing => Direction::Outgoing,
            TraversalDirection::Incoming => Direction::Incoming,
            TraversalDirection::Both => Direction::Both,
        }
    }
}

impl Default for TraversalQuery {
    fn default() -> Self {
        Self {
            max_depth: 3,
            edge_types: Vec::new(),
            direction: TraversalDirection::Outgoing,
            node_filter: None,
            edge_filter: None,
            limit: None,
            labels: Vec::new(),
        }
    }
}

impl TraversalQuery {
    /// Creates a new traversal query.
    pub fn new() -> Self {
        Self::default()
    }

    /// Sets the maximum depth.
    pub fn depth(mut self, depth: usize) -> Self {
        self.max_depth = depth;
        self
    }

    /// Sets edge types to follow.
    pub fn edge_types(mut self, types: Vec<String>) -> Self {
        self.edge_types = types;
        self
    }

    /// Sets the traversal direction.
    pub fn direction(mut self, dir: TraversalDirection) -> Self {
        self.direction = dir;
        self
    }

    /// Sets a node filter.
    pub fn node_filter(mut self, filter: JsonValue) -> Self {
        self.node_filter = Some(filter);
        self
    }

    /// Sets an edge filter.
    pub fn edge_filter(mut self, filter: JsonValue) -> Self {
        self.edge_filter = Some(filter);
        self
    }

    /// Sets result limit.
    pub fn limit(mut self, limit: usize) -> Self {
        self.limit = Some(limit);
        self
    }

    /// Sets label filter.
    pub fn labels(mut self, labels: Vec<String>) -> Self {
        self.labels = labels;
        self
    }
}

/// Result of a traversal operation.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraversalResult {
    /// The node found.
    pub node: Node,
    /// Depth at which this node was found.
    pub depth: usize,
    /// Path from start to this node (node IDs).
    pub path: Vec<NodeId>,
    /// Edges followed to reach this node.
    pub edges: Vec<Edge>,
}

/// Graph traverser for executing traversal queries.
pub struct Traverser<'a> {
    store: &'a GraphStore,
}

impl<'a> Traverser<'a> {
    /// Creates a new traverser.
    pub fn new(store: &'a GraphStore) -> Self {
        Self { store }
    }

    /// Executes a BFS traversal from a starting node.
    pub fn traverse(&self, start: &NodeId, query: &TraversalQuery) -> Vec<TraversalResult> {
        let mut results = Vec::new();
        let mut visited = HashSet::new();
        let mut queue: VecDeque<(NodeId, usize, Vec<NodeId>, Vec<Edge>)> = VecDeque::new();

        visited.insert(*start);
        queue.push_back((*start, 0, vec![*start], Vec::new()));

        let direction: Direction = query.direction.into();

        while let Some((current_id, depth, path, edges_path)) = queue.pop_front() {
            // Check limit
            if let Some(limit) = query.limit {
                if results.len() >= limit {
                    break;
                }
            }

            // Get current node
            if let Some(node) = self.store.get_node(&current_id) {
                // Skip start node in results (depth 0)
                if depth > 0 {
                    // Apply filters
                    if self.matches_query(&node, query) {
                        results.push(TraversalResult {
                            node: node.clone(),
                            depth,
                            path: path.clone(),
                            edges: edges_path.clone(),
                        });
                    }
                }

                // Continue if not at max depth
                if depth < query.max_depth {
                    let edges = self.store.edges_of(&current_id, direction);

                    for edge in edges {
                        // Check edge type filter
                        if !query.edge_types.is_empty() && !query.edge_types.contains(&edge.edge_type) {
                            continue;
                        }

                        // Check edge filter
                        if let Some(ref filter) = query.edge_filter {
                            if !edge.matches_properties(filter) {
                                continue;
                            }
                        }

                        // Get neighbor
                        let neighbor_id = self.get_neighbor(&edge, &current_id, direction);
                        if let Some(next_id) = neighbor_id {
                            if !visited.contains(&next_id) {
                                visited.insert(next_id);
                                let mut new_path = path.clone();
                                new_path.push(next_id);
                                let mut new_edges = edges_path.clone();
                                new_edges.push(edge);
                                queue.push_back((next_id, depth + 1, new_path, new_edges));
                            }
                        }
                    }
                }
            }
        }

        results
    }

    /// Executes a DFS traversal from a starting node.
    pub fn traverse_dfs(&self, start: &NodeId, query: &TraversalQuery) -> Vec<TraversalResult> {
        let mut results = Vec::new();
        let mut visited = HashSet::new();
        let direction: Direction = query.direction.into();

        self.dfs_visit(
            start,
            0,
            vec![*start],
            Vec::new(),
            &mut visited,
            &mut results,
            query,
            direction,
        );

        results
    }

    fn dfs_visit(
        &self,
        current_id: &NodeId,
        depth: usize,
        path: Vec<NodeId>,
        edges_path: Vec<Edge>,
        visited: &mut HashSet<NodeId>,
        results: &mut Vec<TraversalResult>,
        query: &TraversalQuery,
        direction: Direction,
    ) {
        // Check limit
        if let Some(limit) = query.limit {
            if results.len() >= limit {
                return;
            }
        }

        visited.insert(*current_id);

        if let Some(node) = self.store.get_node(current_id) {
            // Skip start node in results
            if depth > 0 && self.matches_query(&node, query) {
                results.push(TraversalResult {
                    node: node.clone(),
                    depth,
                    path: path.clone(),
                    edges: edges_path.clone(),
                });
            }

            // Continue if not at max depth
            if depth < query.max_depth {
                let edges = self.store.edges_of(current_id, direction);

                for edge in edges {
                    // Check edge type filter
                    if !query.edge_types.is_empty() && !query.edge_types.contains(&edge.edge_type) {
                        continue;
                    }

                    // Check edge filter
                    if let Some(ref filter) = query.edge_filter {
                        if !edge.matches_properties(filter) {
                            continue;
                        }
                    }

                    if let Some(next_id) = self.get_neighbor(&edge, current_id, direction) {
                        if !visited.contains(&next_id) {
                            let mut new_path = path.clone();
                            new_path.push(next_id);
                            let mut new_edges = edges_path.clone();
                            new_edges.push(edge);

                            self.dfs_visit(
                                &next_id,
                                depth + 1,
                                new_path,
                                new_edges,
                                visited,
                                results,
                                query,
                                direction,
                            );
                        }
                    }
                }
            }
        }
    }

    /// Checks if a node matches the query filters.
    fn matches_query(&self, node: &Node, query: &TraversalQuery) -> bool {
        // Check labels
        if !query.labels.is_empty() {
            let has_label = query.labels.iter().any(|l| node.has_label(l));
            if !has_label {
                return false;
            }
        }

        // Check node filter
        if let Some(ref filter) = query.node_filter {
            if !node.matches_properties(filter) {
                return false;
            }
        }

        true
    }

    /// Gets the neighbor node ID from an edge.
    fn get_neighbor(&self, edge: &Edge, from: &NodeId, direction: Direction) -> Option<NodeId> {
        match direction {
            Direction::Outgoing => {
                if &edge.source == from {
                    Some(edge.target)
                } else if !edge.directed && &edge.target == from {
                    Some(edge.source)
                } else {
                    None
                }
            }
            Direction::Incoming => {
                if &edge.target == from {
                    Some(edge.source)
                } else if !edge.directed && &edge.source == from {
                    Some(edge.target)
                } else {
                    None
                }
            }
            Direction::Both => {
                if &edge.source == from {
                    Some(edge.target)
                } else if &edge.target == from {
                    Some(edge.source)
                } else {
                    None
                }
            }
        }
    }

    /// Finds all nodes within a certain distance.
    pub fn within_distance(&self, start: &NodeId, max_distance: usize) -> Vec<(Node, usize)> {
        let query = TraversalQuery::new().depth(max_distance);
        self.traverse(start, &query)
            .into_iter()
            .map(|r| (r.node, r.depth))
            .collect()
    }

    /// Finds mutual connections between two nodes.
    pub fn mutual_connections(
        &self,
        node_a: &NodeId,
        node_b: &NodeId,
        edge_type: Option<&str>,
    ) -> Vec<Node> {
        let query = TraversalQuery::new()
            .depth(1)
            .edge_types(edge_type.map(|s| vec![s.to_string()]).unwrap_or_default());

        let neighbors_a: HashSet<NodeId> = self
            .traverse(node_a, &query)
            .into_iter()
            .map(|r| r.node.id)
            .collect();

        let neighbors_b: HashSet<NodeId> = self
            .traverse(node_b, &query)
            .into_iter()
            .map(|r| r.node.id)
            .collect();

        let mutual: HashSet<_> = neighbors_a.intersection(&neighbors_b).collect();

        mutual
            .iter()
            .filter_map(|id| self.store.get_node(id))
            .collect()
    }
}

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

    fn setup_social_graph() -> GraphStore {
        let store = GraphStore::new();

        let alice = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Alice"}));
        let bob = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Bob"}));
        let charlie = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Charlie"}));
        let dave = store.create_node(vec!["User".to_string()], serde_json::json!({"name": "Dave"}));

        // Alice -> Bob -> Charlie -> Dave
        store.create_edge(alice, bob, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(bob, charlie, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(charlie, dave, "FRIEND", serde_json::json!({})).unwrap();

        // Alice -> Charlie (shortcut)
        store.create_edge(alice, charlie, "KNOWS", serde_json::json!({})).unwrap();

        store
    }

    #[test]
    fn test_basic_traversal() {
        let store = setup_social_graph();
        let traverser = Traverser::new(&store);

        let users = store.find_nodes_by_label("User");
        let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap();

        let query = TraversalQuery::new().depth(2);
        let results = traverser.traverse(&alice.id, &query);

        // Should find Bob (depth 1), Charlie (depth 1 and 2), and Dave (depth 2)
        assert!(results.len() >= 2);
    }

    #[test]
    fn test_edge_type_filter() {
        let store = setup_social_graph();
        let traverser = Traverser::new(&store);

        let users = store.find_nodes_by_label("User");
        let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap();

        let query = TraversalQuery::new()
            .depth(2)
            .edge_types(vec!["FRIEND".to_string()]);
        let results = traverser.traverse(&alice.id, &query);

        // Following only FRIEND edges: Alice -> Bob -> Charlie
        let names: Vec<_> = results.iter().filter_map(|r| r.node.get_property("name")).collect();
        assert!(names.contains(&&serde_json::json!("Bob")));
        assert!(names.contains(&&serde_json::json!("Charlie")));
    }

    #[test]
    fn test_depth_limit() {
        let store = setup_social_graph();
        let traverser = Traverser::new(&store);

        let users = store.find_nodes_by_label("User");
        let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap();

        let query = TraversalQuery::new().depth(1);
        let results = traverser.traverse(&alice.id, &query);

        // Depth 1: only direct neighbors
        for result in &results {
            assert_eq!(result.depth, 1);
        }
    }

    #[test]
    fn test_result_limit() {
        let store = setup_social_graph();
        let traverser = Traverser::new(&store);

        let users = store.find_nodes_by_label("User");
        let alice = users.iter().find(|n| n.get_property("name") == Some(&serde_json::json!("Alice"))).unwrap();

        let query = TraversalQuery::new().depth(10).limit(2);
        let results = traverser.traverse(&alice.id, &query);

        assert!(results.len() <= 2);
    }

    #[test]
    fn test_mutual_connections() {
        let store = GraphStore::new();

        let alice = store.create_node(vec![], serde_json::json!({"name": "Alice"}));
        let bob = store.create_node(vec![], serde_json::json!({"name": "Bob"}));
        let mutual1 = store.create_node(vec![], serde_json::json!({"name": "Mutual1"}));
        let mutual2 = store.create_node(vec![], serde_json::json!({"name": "Mutual2"}));
        let only_alice = store.create_node(vec![], serde_json::json!({"name": "OnlyAlice"}));

        store.create_edge(alice, mutual1, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(alice, mutual2, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(alice, only_alice, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(bob, mutual1, "FRIEND", serde_json::json!({})).unwrap();
        store.create_edge(bob, mutual2, "FRIEND", serde_json::json!({})).unwrap();

        let traverser = Traverser::new(&store);
        let mutual = traverser.mutual_connections(&alice, &bob, Some("FRIEND"));

        assert_eq!(mutual.len(), 2);
    }
}
