//! Network Analyzer - Social network analysis algorithms
//!
//! Provides algorithms for analyzing social networks:
//! - N-degree contacts
//! - Mutual friends/tags/items/attributes
//! - Relationship strength calculation
//! - Connection suggestions

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

use crate::types::graph::Entity;
use super::types::*;
use super::store_v2::SocialStoreV2;
use super::path_finder::PathFinder;
use super::item_matcher::ItemMatcher;
use super::attribute_matcher::AttributeMatcher;

/// Network analyzer for social network analysis
pub struct NetworkAnalyzer<'a> {
    store: &'a SocialStoreV2,
}

impl<'a> NetworkAnalyzer<'a> {
    /// Create a new NetworkAnalyzer
    pub fn new(store: &'a SocialStoreV2) -> Self {
        Self { store }
    }
    
    // ========================================================================
    // N-Degree Contacts
    // ========================================================================
    
    /// Get contacts within N degrees of separation
    pub fn get_nth_degree_contacts(
        &self,
        person_id: &str,
        max_degree: usize,
    ) -> DegreeContacts {
        let mut result = DegreeContacts::default();
        let mut visited: HashSet<String> = HashSet::new();
        let mut current_level: Vec<String> = vec![person_id.to_string()];
        
        visited.insert(person_id.to_string());
        
        for degree in 1..=max_degree.min(3) {
            let mut next_level: Vec<String> = Vec::new();
            
            for current_id in &current_level {
                let friends = self.store.get_friends(current_id);
                
                for friend in friends {
                    if !visited.contains(&friend.id) {
                        visited.insert(friend.id.clone());
                        next_level.push(friend.id.clone());
                        
                        match degree {
                            1 => result.degree1.push(friend),
                            2 => result.degree2.push(friend),
                            3 => result.degree3.push(friend),
                            _ => {}
                        }
                    }
                }
            }
            
            current_level = next_level;
        }
        
        result
    }
    
    /// Get the degree of separation between two persons
    pub fn get_degree_of_separation(&self, person_a: &str, person_b: &str) -> Option<usize> {
        let path_finder = PathFinder::new(self.store);
        path_finder.get_distance(person_a, person_b, 6)
    }
    
    // ========================================================================
    // Mutual Connections
    // ========================================================================
    
    /// Get mutual friends between two persons
    pub fn get_mutual_friends(&self, person_a: &str, person_b: &str) -> Vec<Entity> {
        let friends_a: HashSet<String> = self.store.get_friends(person_a)
            .into_iter()
            .map(|f| f.id)
            .collect();
        
        let friends_b: HashSet<String> = self.store.get_friends(person_b)
            .into_iter()
            .map(|f| f.id)
            .collect();
        
        friends_a.intersection(&friends_b)
            .filter_map(|id| self.store.get_person(id))
            .collect()
    }
    
    /// Get mutual tags between two persons
    pub fn get_mutual_tags(&self, person_a: &str, person_b: &str) -> Vec<Entity> {
        let tags_a: HashSet<String> = self.store.get_person_tags(person_a)
            .into_iter()
            .map(|t| t.id)
            .collect();
        
        let tags_b: HashSet<String> = self.store.get_person_tags(person_b)
            .into_iter()
            .map(|t| t.id)
            .collect();
        
        tags_a.intersection(&tags_b)
            .filter_map(|id| self.store.get_tag(id))
            .collect()
    }
    
    /// Get mutual items between two persons
    pub fn get_mutual_items(&self, person_a: &str, person_b: &str) -> Vec<Entity> {
        let items_a: HashSet<String> = self.store.get_person_items(person_a)
            .into_iter()
            .map(|i| i.id)
            .collect();
        
        let items_b: HashSet<String> = self.store.get_person_items(person_b)
            .into_iter()
            .map(|i| i.id)
            .collect();
        
        items_a.intersection(&items_b)
            .filter_map(|id| self.store.get_item(id))
            .collect()
    }
    
    /// Get mutual attributes between two persons
    pub fn get_mutual_attributes(&self, person_a: &str, person_b: &str) -> Vec<Entity> {
        let attrs_a: HashSet<String> = self.store.get_person_attributes(person_a)
            .into_iter()
            .map(|a| a.id)
            .collect();
        
        let attrs_b: HashSet<String> = self.store.get_person_attributes(person_b)
            .into_iter()
            .map(|a| a.id)
            .collect();
        
        attrs_a.intersection(&attrs_b)
            .filter_map(|id| self.store.get_attribute(id))
            .collect()
    }
    
    // ========================================================================
    // Relationship Strength
    // ========================================================================
    
    /// Calculate relationship strength between two persons
    pub fn calculate_relationship_strength(
        &self,
        person_a: &str,
        person_b: &str,
    ) -> RelationshipStrength {
        // 1. Direct relationship strength
        let direct_strength = self.store.get_direct_relation(person_a, person_b)
            .map(|r| r.weight)
            .unwrap_or(0.0);
        
        // 2. Mutual connections
        let mutual_friends = self.get_mutual_friends(person_a, person_b);
        let mutual_friends_count = mutual_friends.len();
        
        let mutual_tags = self.get_mutual_tags(person_a, person_b);
        let mutual_tags_count = mutual_tags.len();
        
        let mutual_items = self.get_mutual_items(person_a, person_b);
        let mutual_items_count = mutual_items.len();
        
        let mutual_attributes = self.get_mutual_attributes(person_a, person_b);
        let mutual_attributes_count = mutual_attributes.len();
        
        // 3. Shortest path length
        let path_finder = PathFinder::new(self.store);
        let shortest_path_length = path_finder.get_distance(person_a, person_b, 6)
            .unwrap_or(usize::MAX);
        
        // 4. Calculate overall score
        let overall_score = self.calculate_overall_score(
            direct_strength,
            mutual_friends_count,
            mutual_tags_count,
            mutual_items_count,
            mutual_attributes_count,
            shortest_path_length,
        );
        
        RelationshipStrength {
            direct_strength,
            mutual_friends_count,
            mutual_tags_count,
            mutual_items_count,
            mutual_attributes_count,
            shortest_path_length,
            overall_score,
        }
    }
    
    /// Calculate overall relationship score
    fn calculate_overall_score(
        &self,
        direct: f64,
        mutual_friends: usize,
        mutual_tags: usize,
        mutual_items: usize,
        mutual_attributes: usize,
        path_length: usize,
    ) -> f64 {
        let friend_factor = (mutual_friends as f64).ln_1p() * 0.15;
        let tag_factor = (mutual_tags as f64).ln_1p() * 0.1;
        let item_factor = (mutual_items as f64).ln_1p() * 0.1;
        let attr_factor = (mutual_attributes as f64).ln_1p() * 0.15;
        
        let path_factor = if path_length < usize::MAX {
            1.0 / (path_length as f64 + 1.0)
        } else {
            0.0
        };
        
        (direct * 0.4 + friend_factor + tag_factor + item_factor + attr_factor + path_factor * 0.1).min(1.0)
    }
    
    // ========================================================================
    // Connection Suggestions
    // ========================================================================
    
    /// Suggest connections for a person
    pub fn suggest_connections(
        &self,
        person_id: &str,
        config: Option<SuggestionConfig>,
    ) -> Vec<ConnectionSuggestion> {
        let cfg = config.unwrap_or_default();
        let mut candidates: HashMap<String, ConnectionSuggestion> = HashMap::new();
        
        // Strategy 1: Friends of friends (mutual friends)
        self.add_mutual_friend_candidates(person_id, &mut candidates, &cfg);
        
        // Strategy 2: Same tag/organization members
        self.add_tag_candidates(person_id, &mut candidates, &cfg);
        
        // Strategy 3: Shared item owners
        self.add_item_candidates(person_id, &mut candidates, &cfg);
        
        // Strategy 4: Shared attribute holders
        self.add_attribute_candidates(person_id, &mut candidates, &cfg);
        
        // Filter and sort
        self.rank_and_filter_candidates(person_id, candidates, &cfg)
    }
    
    /// Add candidates based on mutual friends
    fn add_mutual_friend_candidates(
        &self,
        person_id: &str,
        candidates: &mut HashMap<String, ConnectionSuggestion>,
        config: &SuggestionConfig,
    ) {
        let degree_contacts = self.get_nth_degree_contacts(person_id, 2);
        
        for person in degree_contacts.degree2 {
            let mutual = self.get_mutual_friends(person_id, &person.id);
            if !mutual.is_empty() {
                let score = (0.5 + (mutual.len() as f64 * 0.1).min(0.5)) * config.friend_weight;
                
                candidates.entry(person.id.clone())
                    .and_modify(|c| {
                        c.score += score;
                        c.reasons.push(SuggestionReason::MutualFriend { count: mutual.len() });
                    })
                    .or_insert(ConnectionSuggestion {
                        person: person.clone(),
                        score,
                        reasons: vec![SuggestionReason::MutualFriend { count: mutual.len() }],
                        via: mutual.first().cloned(),
                    });
            }
        }
    }
    
    /// Add candidates based on shared tags
    fn add_tag_candidates(
        &self,
        person_id: &str,
        candidates: &mut HashMap<String, ConnectionSuggestion>,
        config: &SuggestionConfig,
    ) {
        let my_tags = self.store.get_person_tags(person_id);
        
        for tag in my_tags {
            let members = self.store.get_tag_members(&tag.id);
            
            for member in members {
                if member.id != person_id {
                    let score = 0.6 * config.tag_weight;
                    
                    candidates.entry(member.id.clone())
                        .and_modify(|c| {
                            c.score += score;
                            c.reasons.push(SuggestionReason::MutualTag { tag_name: tag.name.clone() });
                        })
                        .or_insert(ConnectionSuggestion {
                            person: member.clone(),
                            score,
                            reasons: vec![SuggestionReason::MutualTag { tag_name: tag.name.clone() }],
                            via: None,
                        });
                }
            }
        }
    }
    
    /// Add candidates based on shared items
    fn add_item_candidates(
        &self,
        person_id: &str,
        candidates: &mut HashMap<String, ConnectionSuggestion>,
        config: &SuggestionConfig,
    ) {
        let item_matcher = ItemMatcher::new(self.store);
        let matches = item_matcher.find_people_by_shared_items(person_id, None);
        
        for m in matches {
            let reason = if m.shared_items.len() == 1 {
                SuggestionReason::SharedItem { item_name: m.shared_items[0].name.clone() }
            } else {
                SuggestionReason::SharedItem { 
                    item_name: format!("{}件共有物品", m.shared_items.len()) 
                }
            };
            
            let score = m.match_score * config.item_weight;
            
            candidates.entry(m.person.id.clone())
                .and_modify(|c| {
                    c.score += score;
                    c.reasons.push(reason.clone());
                })
                .or_insert(ConnectionSuggestion {
                    person: m.person,
                    score,
                    reasons: vec![reason],
                    via: None,
                });
        }
    }
    
    /// Add candidates based on shared attributes
    fn add_attribute_candidates(
        &self,
        person_id: &str,
        candidates: &mut HashMap<String, ConnectionSuggestion>,
        config: &SuggestionConfig,
    ) {
        let attr_matcher = AttributeMatcher::new(self.store);
        let matches = attr_matcher.find_people_by_shared_attributes(
            person_id,
            Some(config.attribute_config.clone()),
        );
        
        for m in matches {
            // Generate suggestion reasons from attributes
            let reasons: Vec<SuggestionReason> = m.shared_attributes.iter()
                .map(|attr| self.attribute_to_reason(attr))
                .collect();
            
            let score = m.match_score * config.attribute_weight;
            
            candidates.entry(m.person.id.clone())
                .and_modify(|c| {
                    c.score += score;
                    c.reasons.extend(reasons.clone());
                })
                .or_insert(ConnectionSuggestion {
                    person: m.person,
                    score,
                    reasons,
                    via: None,
                });
        }
    }
    
    /// Convert shared attribute to suggestion reason
    fn attribute_to_reason(&self, attr: &SharedAttributeDetail) -> SuggestionReason {
        match attr.category.as_str() {
            "school" | "education" => SuggestionReason::SameSchool { school: attr.attribute.name.clone() },
            "city" | "location" => SuggestionReason::SameCity { city: attr.attribute.name.clone() },
            "zodiac" => SuggestionReason::SameZodiac { zodiac: attr.attribute.name.clone() },
            "hobby" => SuggestionReason::SameHobby { hobby: attr.attribute.name.clone() },
            "industry" => SuggestionReason::SameIndustry { industry: attr.attribute.name.clone() },
            "mbti" => SuggestionReason::SameMBTI { mbti: attr.attribute.name.clone() },
            _ => SuggestionReason::SimilarProfile,
        }
    }
    
    /// Rank and filter candidates
    fn rank_and_filter_candidates(
        &self,
        person_id: &str,
        candidates: HashMap<String, ConnectionSuggestion>,
        config: &SuggestionConfig,
    ) -> Vec<ConnectionSuggestion> {
        let mut results: Vec<ConnectionSuggestion> = candidates.into_values()
            .filter(|c| {
                // Exclude self
                if c.person.id == person_id {
                    return false;
                }
                
                // Exclude existing friends if configured
                if config.exclude_existing_friends && self.store.are_friends(person_id, &c.person.id) {
                    return false;
                }
                
                // Apply minimum score filter
                if c.score < config.min_score {
                    return false;
                }
                
                true
            })
            .collect();
        
        // Sort by score descending
        results.sort_by(|a, b| b.score.partial_cmp(&a.score).unwrap_or(std::cmp::Ordering::Equal));
        
        // Truncate to max results
        results.truncate(config.max_results);
        
        results
    }
    
    // ========================================================================
    // Network Statistics
    // ========================================================================
    
    /// Get network statistics for a person
    pub fn get_network_stats(&self, person_id: &str) -> NetworkStats {
        let friends = self.store.get_friends(person_id);
        let tags = self.store.get_person_tags(person_id);
        let items = self.store.get_person_items(person_id);
        let attributes = self.store.get_person_attributes(person_id);
        
        let degree_contacts = self.get_nth_degree_contacts(person_id, 3);
        
        // Calculate average path length (sample-based for performance)
        let avg_path_length = self.calculate_avg_path_length(person_id, &degree_contacts);
        
        // Calculate clustering coefficient
        let clustering_coefficient = self.calculate_clustering_coefficient(person_id, &friends);
        
        NetworkStats {
            total_friends: friends.len(),
            total_tags: tags.len(),
            total_items: items.len(),
            total_attributes: attributes.len(),
            network_reach_2hop: degree_contacts.degree1.len() + degree_contacts.degree2.len(),
            network_reach_3hop: degree_contacts.degree1.len() + degree_contacts.degree2.len() + degree_contacts.degree3.len(),
            avg_path_length,
            clustering_coefficient,
        }
    }
    
    /// Calculate average path length (sample-based)
    fn calculate_avg_path_length(&self, person_id: &str, contacts: &DegreeContacts) -> f64 {
        let path_finder = PathFinder::new(self.store);
        let mut total_length = 0usize;
        let mut count = 0usize;
        
        // Sample from each degree
        let sample_size = 10;
        
        for friend in contacts.degree1.iter().take(sample_size) {
            if let Some(dist) = path_finder.get_distance(person_id, &friend.id, 1) {
                total_length += dist;
                count += 1;
            }
        }
        
        for friend in contacts.degree2.iter().take(sample_size) {
            if let Some(dist) = path_finder.get_distance(person_id, &friend.id, 2) {
                total_length += dist;
                count += 1;
            }
        }
        
        for friend in contacts.degree3.iter().take(sample_size) {
            if let Some(dist) = path_finder.get_distance(person_id, &friend.id, 3) {
                total_length += dist;
                count += 1;
            }
        }
        
        if count > 0 {
            total_length as f64 / count as f64
        } else {
            0.0
        }
    }
    
    /// Calculate clustering coefficient
    fn calculate_clustering_coefficient(&self, _person_id: &str, friends: &[Entity]) -> f64 {
        if friends.len() < 2 {
            return 0.0;
        }
        
        let friend_ids: HashSet<String> = friends.iter().map(|f| f.id.clone()).collect();
        let mut edges = 0usize;
        
        // Count edges between friends
        for friend in friends {
            let friend_friends = self.store.get_friends(&friend.id);
            for ff in friend_friends {
                if friend_ids.contains(&ff.id) {
                    edges += 1;
                }
            }
        }
        
        // Each edge is counted twice
        edges /= 2;
        
        let possible_edges = friends.len() * (friends.len() - 1) / 2;
        
        if possible_edges > 0 {
            edges as f64 / possible_edges as f64
        } else {
            0.0
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use super::super::store_v2::SocialStoreV2Config;
    use super::super::types::{PersonInput, FriendConfig, TagInput};
    
    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_nth_degree_contacts() {
        let store = create_test_store();
        
        // Create chain: A -> B -> C -> D
        let persons: Vec<_> = ["A", "B", "C", "D"]
            .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..3 {
            store.add_friend(&persons[i].id, &persons[i+1].id, FriendConfig::default());
        }
        
        let analyzer = NetworkAnalyzer::new(&store);
        let contacts = analyzer.get_nth_degree_contacts(&persons[0].id, 3);
        
        assert_eq!(contacts.degree1.len(), 1); // B
        assert_eq!(contacts.degree2.len(), 1); // C
        assert_eq!(contacts.degree3.len(), 1); // D
    }
    
    #[test]
    fn test_mutual_friends() {
        let store = create_test_store();
        
        // A and C both know B
        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(&c.id, &b.id, FriendConfig::default());
        
        let analyzer = NetworkAnalyzer::new(&store);
        let mutual = analyzer.get_mutual_friends(&a.id, &c.id);
        
        assert_eq!(mutual.len(), 1);
        assert_eq!(mutual[0].name, "B");
    }
    
    #[test]
    fn test_relationship_strength() {
        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(),
        });
        
        store.add_friend(&a.id, &b.id, FriendConfig {
            weight: 0.8,
            ..FriendConfig::default()
        });
        
        let analyzer = NetworkAnalyzer::new(&store);
        let strength = analyzer.calculate_relationship_strength(&a.id, &b.id);
        
        assert_eq!(strength.direct_strength, 0.8);
        assert_eq!(strength.shortest_path_length, 1);
        assert!(strength.overall_score > 0.0);
    }
    
    #[test]
    fn test_suggest_connections() {
        let store = create_test_store();
        
        // A knows B, B knows C
        // A and C share a tag
        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 tag = store.add_tag(TagInput {
            id: None, name: "Engineers".to_string(),
            tag_type: "tag".to_string(), description: None, color: None,
            parent_tags: vec![],
            properties: std::collections::BTreeMap::new(),
        });
        
        store.tag_person(&a.id, &tag.id);
        store.tag_person(&c.id, &tag.id);
        
        let analyzer = NetworkAnalyzer::new(&store);
        let suggestions = analyzer.suggest_connections(&a.id, None);
        
        // C should be suggested (mutual friend B + shared tag)
        assert!(!suggestions.is_empty());
        let c_suggestion = suggestions.iter().find(|s| s.person.id == c.id);
        assert!(c_suggestion.is_some());
    }
    
    #[test]
    fn test_network_stats() {
        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(),
        });
        
        store.add_friend(&a.id, &b.id, FriendConfig::default());
        
        let tag = store.add_tag(TagInput {
            id: None, name: "Tag1".to_string(),
            tag_type: "tag".to_string(), description: None, color: None,
            parent_tags: vec![],
            properties: std::collections::BTreeMap::new(),
        });
        store.tag_person(&a.id, &tag.id);
        
        let analyzer = NetworkAnalyzer::new(&store);
        let stats = analyzer.get_network_stats(&a.id);
        
        assert_eq!(stats.total_friends, 1);
        assert_eq!(stats.total_tags, 1);
    }
}
