//! Path Finder - Social path discovery algorithms
//!
//! Provides algorithms for finding paths between people in a social network:
//! - Shortest path (BFS)
//! - Bidirectional BFS (optimized)
//! - All paths finding
//! - Intermediary discovery

use std::collections::{HashMap, HashSet, VecDeque};

use crate::types::graph::{Entity, Relation, relation_types};
use super::types::{PathResult, AllPathsResult, PathOptions};
use super::store_v2::SocialStoreV2;

/// Path finder for social network traversal
pub struct PathFinder<'a> {
    store: &'a SocialStoreV2,
}

impl<'a> PathFinder<'a> {
    /// Create a new PathFinder
    pub fn new(store: &'a SocialStoreV2) -> Self {
        Self { store }
    }
    
    /// Find shortest path between two persons using BFS
    pub fn find_shortest_path(
        &self,
        from_id: &str,
        to_id: &str,
        options: Option<PathOptions>,
    ) -> Option<PathResult> {
        let opts = options.unwrap_or_default();
        let max_hops = opts.max_hops.unwrap_or(6);
        
        if opts.bidirectional_search {
            return self.bidirectional_bfs(from_id, to_id, max_hops, opts.relation_types.as_deref());
        }
        
        self.bfs_path(from_id, to_id, max_hops, opts.relation_types.as_deref())
    }
    
    /// Standard BFS path finding
    fn bfs_path(
        &self,
        from_id: &str,
        to_id: &str,
        max_hops: usize,
        relation_types_filter: Option<&[String]>,
    ) -> Option<PathResult> {
        if from_id == to_id {
            if let Some(entity) = self.store.get_person(from_id) {
                return Some(PathResult {
                    path: vec![entity],
                    hops: 0,
                    intermediaries: vec![],
                    relation_chain: vec![],
                });
            }
            return None;
        }
        
        // (current_id, path_ids, relations)
        let mut queue: VecDeque<(String, Vec<String>, Vec<Relation>)> = VecDeque::new();
        let mut visited: HashSet<String> = HashSet::new();
        
        queue.push_back((from_id.to_string(), vec![from_id.to_string()], vec![]));
        visited.insert(from_id.to_string());
        
        while let Some((current_id, path, relations)) = queue.pop_front() {
            if path.len() > max_hops + 1 {
                continue;
            }
            
            // Get all neighbors
            let neighbors = self.get_social_neighbors(&current_id, relation_types_filter);
            
            for (neighbor_id, relation) in neighbors {
                if neighbor_id == to_id {
                    // Found the target
                    let mut full_path = path.clone();
                    full_path.push(neighbor_id.clone());
                    
                    let mut full_relations = relations.clone();
                    full_relations.push(relation);
                    
                    return Some(self.build_path_result(full_path, full_relations));
                }
                
                if !visited.contains(&neighbor_id) && path.len() < max_hops {
                    visited.insert(neighbor_id.clone());
                    
                    let mut new_path = path.clone();
                    new_path.push(neighbor_id.clone());
                    
                    let mut new_relations = relations.clone();
                    new_relations.push(relation);
                    
                    queue.push_back((neighbor_id, new_path, new_relations));
                }
            }
        }
        
        None
    }
    
    /// Bidirectional BFS - search from both ends simultaneously
    pub fn bidirectional_bfs(
        &self,
        from_id: &str,
        to_id: &str,
        max_hops: usize,
        relation_types_filter: Option<&[String]>,
    ) -> Option<PathResult> {
        if from_id == to_id {
            if let Some(entity) = self.store.get_person(from_id) {
                return Some(PathResult {
                    path: vec![entity],
                    hops: 0,
                    intermediaries: vec![],
                    relation_chain: vec![],
                });
            }
            return None;
        }
        
        // Forward search state: id -> (distance, parent_id, relation)
        let mut forward_visited: HashMap<String, (usize, Option<String>, Option<Relation>)> = HashMap::new();
        let mut forward_queue: VecDeque<String> = VecDeque::new();
        
        // Backward search state: id -> (distance, child_id, relation)
        let mut backward_visited: HashMap<String, (usize, Option<String>, Option<Relation>)> = HashMap::new();
        let mut backward_queue: VecDeque<String> = VecDeque::new();
        
        forward_visited.insert(from_id.to_string(), (0, None, None));
        backward_visited.insert(to_id.to_string(), (0, None, None));
        forward_queue.push_back(from_id.to_string());
        backward_queue.push_back(to_id.to_string());
        
        let mut meeting_point: Option<String> = None;
        let mut best_length = usize::MAX;
        
        while !forward_queue.is_empty() || !backward_queue.is_empty() {
            // Expand forward
            if let Some(current) = forward_queue.pop_front() {
                let current_dist = forward_visited.get(&current).map(|x| x.0).unwrap_or(0);
                
                if current_dist < max_hops / 2 + 1 {
                    for (neighbor_id, relation) in self.get_social_neighbors(&current, relation_types_filter) {
                        if !forward_visited.contains_key(&neighbor_id) {
                            forward_visited.insert(
                                neighbor_id.clone(),
                                (current_dist + 1, Some(current.clone()), Some(relation)),
                            );
                            forward_queue.push_back(neighbor_id.clone());
                            
                            // Check if we meet backward search
                            if let Some((back_dist, _, _)) = backward_visited.get(&neighbor_id) {
                                let total = current_dist + 1 + back_dist;
                                if total < best_length {
                                    best_length = total;
                                    meeting_point = Some(neighbor_id);
                                }
                            }
                        }
                    }
                }
            }
            
            // Expand backward
            if let Some(current) = backward_queue.pop_front() {
                let current_dist = backward_visited.get(&current).map(|x| x.0).unwrap_or(0);
                
                if current_dist < max_hops / 2 + 1 {
                    for (neighbor_id, relation) in self.get_social_neighbors(&current, relation_types_filter) {
                        if !backward_visited.contains_key(&neighbor_id) {
                            backward_visited.insert(
                                neighbor_id.clone(),
                                (current_dist + 1, Some(current.clone()), Some(relation)),
                            );
                            backward_queue.push_back(neighbor_id.clone());
                            
                            // Check if we meet forward search
                            if let Some((fwd_dist, _, _)) = forward_visited.get(&neighbor_id) {
                                let total = current_dist + 1 + fwd_dist;
                                if total < best_length {
                                    best_length = total;
                                    meeting_point = Some(neighbor_id);
                                }
                            }
                        }
                    }
                }
            }
            
            // Early termination if we found a path and explored enough
            if meeting_point.is_some() && best_length <= max_hops {
                break;
            }
        }
        
        // Reconstruct path
        if let Some(meet) = meeting_point {
            self.reconstruct_bidirectional_path(
                &meet,
                from_id,
                to_id,
                &forward_visited,
                &backward_visited,
            )
        } else {
            None
        }
    }
    
    /// Reconstruct path from bidirectional search
    fn reconstruct_bidirectional_path(
        &self,
        meeting_point: &str,
        from_id: &str,
        to_id: &str,
        forward_visited: &HashMap<String, (usize, Option<String>, Option<Relation>)>,
        backward_visited: &HashMap<String, (usize, Option<String>, Option<Relation>)>,
    ) -> Option<PathResult> {
        let mut path_ids: Vec<String> = Vec::new();
        let mut relations: Vec<Relation> = Vec::new();
        
        // Build forward path (from start to meeting point)
        let mut current = meeting_point.to_string();
        let mut forward_segment: Vec<(String, Option<Relation>)> = Vec::new();
        
        while current != from_id {
            if let Some((_, parent, rel)) = forward_visited.get(&current) {
                forward_segment.push((current.clone(), rel.clone()));
                if let Some(p) = parent {
                    current = p.clone();
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        
        // Add start
        path_ids.push(from_id.to_string());
        
        // Add forward segment in reverse
        forward_segment.reverse();
        for (id, rel) in forward_segment {
            if let Some(r) = rel {
                relations.push(r);
            }
            path_ids.push(id);
        }
        
        // Build backward path (from meeting point to end)
        current = meeting_point.to_string();
        while current != to_id {
            if let Some((_, child, rel)) = backward_visited.get(&current) {
                if let Some(c) = child {
                    if let Some(r) = rel {
                        relations.push(r.clone());
                    }
                    path_ids.push(c.clone());
                    current = c.clone();
                } else {
                    break;
                }
            } else {
                break;
            }
        }
        
        // Remove duplicate meeting point if present
        let mut deduped_path: Vec<String> = Vec::new();
        for id in path_ids {
            if deduped_path.last() != Some(&id) {
                deduped_path.push(id);
            }
        }
        
        if deduped_path.is_empty() {
            return None;
        }
        
        Some(self.build_path_result(deduped_path, relations))
    }
    
    /// Find all paths between two persons (up to max_paths)
    pub fn find_all_paths(
        &self,
        from_id: &str,
        to_id: &str,
        max_hops: usize,
        max_paths: usize,
    ) -> AllPathsResult {
        let mut paths: Vec<PathResult> = Vec::new();
        let mut shortest_hops = usize::MAX;
        
        // DFS with pruning
        let mut stack: Vec<(String, Vec<String>, Vec<Relation>, HashSet<String>)> = Vec::new();
        
        let mut initial_visited = HashSet::new();
        initial_visited.insert(from_id.to_string());
        stack.push((from_id.to_string(), vec![from_id.to_string()], vec![], initial_visited));
        
        while let Some((current_id, path, relations, visited)) = stack.pop() {
            if paths.len() >= max_paths {
                break;
            }
            
            if path.len() > max_hops + 1 {
                continue;
            }
            
            let neighbors = self.get_social_neighbors(&current_id, None);
            
            for (neighbor_id, relation) in neighbors {
                if neighbor_id == to_id {
                    // Found a path
                    let mut full_path = path.clone();
                    full_path.push(neighbor_id.clone());
                    
                    let mut full_relations = relations.clone();
                    full_relations.push(relation);
                    
                    let result = self.build_path_result(full_path, full_relations);
                    if result.hops < shortest_hops {
                        shortest_hops = result.hops;
                    }
                    paths.push(result);
                    
                    if paths.len() >= max_paths {
                        break;
                    }
                } else if !visited.contains(&neighbor_id) && path.len() < max_hops {
                    let mut new_path = path.clone();
                    new_path.push(neighbor_id.clone());
                    
                    let mut new_relations = relations.clone();
                    new_relations.push(relation);
                    
                    let mut new_visited = visited.clone();
                    new_visited.insert(neighbor_id.clone());
                    
                    stack.push((neighbor_id, new_path, new_relations, new_visited));
                }
            }
        }
        
        // Sort by hops
        paths.sort_by_key(|p| p.hops);
        
        AllPathsResult {
            paths,
            shortest_hops: if shortest_hops == usize::MAX { 0 } else { shortest_hops },
        }
    }
    
    /// Find all intermediaries between two persons
    pub fn find_intermediaries(
        &self,
        person_a: &str,
        person_b: &str,
        max_hops: usize,
    ) -> Vec<Entity> {
        let all_paths = self.find_all_paths(person_a, person_b, max_hops, 100);
        
        let mut intermediary_ids: HashSet<String> = HashSet::new();
        
        for path in all_paths.paths {
            for entity in &path.intermediaries {
                intermediary_ids.insert(entity.id.clone());
            }
        }
        
        intermediary_ids
            .into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    /// Check if a path exists between two persons
    pub fn path_exists(&self, from_id: &str, to_id: &str, max_hops: usize) -> bool {
        self.find_shortest_path(from_id, to_id, Some(PathOptions {
            max_hops: Some(max_hops),
            relation_types: None,
            bidirectional_search: true,
        })).is_some()
    }
    
    /// Get the shortest distance between two persons
    pub fn get_distance(&self, from_id: &str, to_id: &str, max_hops: usize) -> Option<usize> {
        self.find_shortest_path(from_id, to_id, Some(PathOptions {
            max_hops: Some(max_hops),
            relation_types: None,
            bidirectional_search: true,
        })).map(|p| p.hops)
    }
    
    // ========================================================================
    // Helper Methods
    // ========================================================================
    
    /// Get social neighbors (friends) of a person
    fn get_social_neighbors(
        &self,
        person_id: &str,
        relation_types_filter: Option<&[String]>,
    ) -> Vec<(String, Relation)> {
        let mut neighbors = Vec::new();
        
        // Outgoing relations
        for rel in self.store.outgoing_from(person_id) {
            if self.is_social_relation(&rel.relation_type, relation_types_filter) {
                if self.store.get_person(&rel.target).is_some() {
                    neighbors.push((rel.target.clone(), rel));
                }
            }
        }
        
        // Incoming bidirectional relations
        for rel in self.store.incoming_to(person_id) {
            if rel.bidirectional && self.is_social_relation(&rel.relation_type, relation_types_filter) {
                if self.store.get_person(&rel.source).is_some() {
                    neighbors.push((rel.source.clone(), rel));
                }
            }
        }
        
        neighbors
    }
    
    /// Check if relation type is a social relation
    fn is_social_relation(&self, rel_type: &str, filter: Option<&[String]>) -> bool {
        let is_social = matches!(rel_type,
            relation_types::FRIEND |
            relation_types::KNOWS |
            relation_types::COLLEAGUE |
            relation_types::CLASSMATE |
            relation_types::FAMILY |
            relation_types::MENTOR
        );
        
        if let Some(types) = filter {
            is_social && types.iter().any(|t| t == rel_type)
        } else {
            is_social
        }
    }
    
    /// Build PathResult from path IDs and relations
    fn build_path_result(&self, path_ids: Vec<String>, relations: Vec<Relation>) -> PathResult {
        let path: Vec<Entity> = path_ids
            .iter()
            .filter_map(|id| self.store.get_person(id))
            .collect();
        
        let hops = if path.len() > 0 { path.len() - 1 } else { 0 };
        
        let intermediaries: Vec<Entity> = if path.len() > 2 {
            path[1..path.len()-1].to_vec()
        } else {
            vec![]
        };
        
        PathResult {
            path,
            hops,
            intermediaries,
            relation_chain: relations,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use super::super::store_v2::SocialStoreV2Config;
    use super::super::types::{PersonInput, FriendConfig};
    
    fn create_test_store() -> SocialStoreV2 {
        let dir = tempdir().unwrap();
        let config = SocialStoreV2Config::new(dir.path())
            .with_shard_count(4)
            .with_wal(false);
        SocialStoreV2::new(config)
    }
    
    #[test]
    fn test_find_shortest_path() {
        let store = create_test_store();
        
        // Create A -> B -> C chain
        let a = store.add_person(PersonInput {
            id: None,
            name: "A".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let b = store.add_person(PersonInput {
            id: None,
            name: "B".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let c = store.add_person(PersonInput {
            id: None,
            name: "C".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_friend(&a.id, &b.id, FriendConfig::default());
        store.add_friend(&b.id, &c.id, FriendConfig::default());
        
        let finder = PathFinder::new(&store);
        
        // Find path from A to C
        let path = finder.find_shortest_path(&a.id, &c.id, None).unwrap();
        assert_eq!(path.hops, 2);
        assert_eq!(path.path.len(), 3);
        assert_eq!(path.intermediaries.len(), 1);
        assert_eq!(path.intermediaries[0].name, "B");
    }
    
    #[test]
    fn test_bidirectional_bfs() {
        let store = create_test_store();
        
        // Create longer chain: A -> B -> C -> D -> E
        let persons: Vec<_> = ["A", "B", "C", "D", "E"]
            .iter()
            .map(|name| {
                store.add_person(PersonInput {
                    id: None,
                    name: name.to_string(),
                    avatar: None, description: None, phone: None, email: None, wechat: None,
                    properties: std::collections::BTreeMap::new(),
                })
            })
            .collect();
        
        for i in 0..4 {
            store.add_friend(&persons[i].id, &persons[i+1].id, FriendConfig::default());
        }
        
        let finder = PathFinder::new(&store);
        
        // Find path using bidirectional BFS
        let path = finder.bidirectional_bfs(&persons[0].id, &persons[4].id, 6, None).unwrap();
        assert_eq!(path.hops, 4);
        assert_eq!(path.path.len(), 5);
    }
    
    #[test]
    fn test_find_all_paths() {
        let store = create_test_store();
        
        // Create graph with multiple paths
        //   B
        //  / \
        // A   D
        //  \ /
        //   C
        let a = store.add_person(PersonInput {
            id: None, name: "A".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let b = store.add_person(PersonInput {
            id: None, name: "B".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let c = store.add_person(PersonInput {
            id: None, name: "C".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let d = store.add_person(PersonInput {
            id: None, name: "D".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_friend(&a.id, &b.id, FriendConfig::default());
        store.add_friend(&a.id, &c.id, FriendConfig::default());
        store.add_friend(&b.id, &d.id, FriendConfig::default());
        store.add_friend(&c.id, &d.id, FriendConfig::default());
        
        let finder = PathFinder::new(&store);
        
        let result = finder.find_all_paths(&a.id, &d.id, 3, 10);
        assert_eq!(result.paths.len(), 2); // A->B->D and A->C->D
        assert_eq!(result.shortest_hops, 2);
    }
    
    #[test]
    fn test_find_intermediaries() {
        let store = create_test_store();
        
        let a = store.add_person(PersonInput {
            id: None, name: "A".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let b = store.add_person(PersonInput {
            id: None, name: "B".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let c = store.add_person(PersonInput {
            id: None, name: "C".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_friend(&a.id, &b.id, FriendConfig::default());
        store.add_friend(&b.id, &c.id, FriendConfig::default());
        
        let finder = PathFinder::new(&store);
        
        let intermediaries = finder.find_intermediaries(&a.id, &c.id, 3);
        assert_eq!(intermediaries.len(), 1);
        assert_eq!(intermediaries[0].name, "B");
    }
}
