//! Attribute Matcher - Shared attribute discovery and compatibility analysis
//!
//! Provides algorithms for finding connections through shared attributes:
//! - Shared attribute discovery between people
//! - Finding people by attributes (alumni, same city, same zodiac, etc.)
//! - Attribute similarity and compatibility analysis
//! - Popular attributes discovery

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

use crate::types::graph::Entity;
use super::types::*;
use super::store_v2::SocialStoreV2;

/// Attribute matcher for shared attribute discovery
pub struct AttributeMatcher<'a> {
    store: &'a SocialStoreV2,
}

impl<'a> AttributeMatcher<'a> {
    /// Create a new AttributeMatcher
    pub fn new(store: &'a SocialStoreV2) -> Self {
        Self { store }
    }
    
    // ========================================================================
    // Basic Attribute Queries
    // ========================================================================
    
    /// Get all attributes of a person
    pub fn get_person_attributes(&self, person_id: &str, category: Option<&str>) -> Vec<Entity> {
        let attrs = self.store.get_person_attributes(person_id);
        
        if let Some(cat) = category {
            attrs.into_iter()
                .filter(|attr| {
                    attr.properties.get("category")
                        .and_then(|v| v.as_str())
                        .map(|c| c == cat)
                        .unwrap_or(false)
                })
                .collect()
        } else {
            attrs
        }
    }
    
    /// Get all persons with a specific attribute
    pub fn get_persons_by_attribute(&self, attr_id: &str) -> Vec<Entity> {
        self.store.get_persons_by_attribute(attr_id)
    }
    
    // ========================================================================
    // Shared Attribute Discovery
    // ========================================================================
    
    /// Find shared attributes between two people
    pub fn find_shared_attributes(
        &self,
        person_a: &str,
        person_b: &str,
    ) -> Vec<SharedAttributeDetail> {
        let attrs_a = self.store.get_person_attributes(person_a);
        let attrs_b: HashSet<String> = self.store.get_person_attributes(person_b)
            .into_iter()
            .map(|a| a.id)
            .collect();
        
        let config = AttributeMatchConfig::default();
        
        attrs_a.into_iter()
            .filter(|attr| attrs_b.contains(&attr.id))
            .map(|attr| {
                let category = attr.properties.get("category")
                    .and_then(|v| v.as_str())
                    .unwrap_or("unknown")
                    .to_string();
                
                let weight = config.category_weights
                    .get(&category)
                    .copied()
                    .unwrap_or(0.5);
                
                SharedAttributeDetail {
                    attribute: attr,
                    category,
                    weight,
                }
            })
            .collect()
    }
    
    /// Find people who share attributes with a given person
    pub fn find_people_by_shared_attributes(
        &self,
        person_id: &str,
        config: Option<AttributeMatchConfig>,
    ) -> Vec<AttributeMatchResult> {
        let cfg = config.unwrap_or_default();
        let my_attrs = self.store.get_person_attributes(person_id);
        
        if my_attrs.is_empty() {
            return vec![];
        }
        
        // Filter attributes by config
        let filtered_attrs: Vec<Entity> = my_attrs.into_iter()
            .filter(|attr| {
                let category = attr.properties.get("category")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                
                // Check include filter
                if let Some(ref include) = cfg.include_categories {
                    if !include.iter().any(|c| c == category) {
                        return false;
                    }
                }
                
                // Check exclude filter
                if let Some(ref exclude) = cfg.exclude_categories {
                    if exclude.iter().any(|c| c == category) {
                        return false;
                    }
                }
                
                true
            })
            .collect();
        
        if filtered_attrs.is_empty() {
            return vec![];
        }
        
        // Track candidates and their shared attributes
        let mut candidates: HashMap<String, Vec<SharedAttributeDetail>> = HashMap::new();
        
        for attr in &filtered_attrs {
            let category = attr.properties.get("category")
                .and_then(|v| v.as_str())
                .unwrap_or("unknown")
                .to_string();
            
            let weight = cfg.category_weights
                .get(&category)
                .copied()
                .unwrap_or(0.5);
            
            let persons = self.get_persons_by_attribute(&attr.id);
            for person in persons {
                if person.id != person_id {
                    candidates.entry(person.id.clone())
                        .or_default()
                        .push(SharedAttributeDetail {
                            attribute: attr.clone(),
                            category: category.clone(),
                            weight,
                        });
                }
            }
        }
        
        // Build results
        let mut results: Vec<AttributeMatchResult> = candidates.into_iter()
            .filter(|(_, attrs)| attrs.len() >= cfg.min_match_count)
            .filter_map(|(person_id, shared_attrs)| {
                let person = self.store.get_person(&person_id)?;
                let match_score = self.calculate_attribute_match_score(&shared_attrs);
                
                Some(AttributeMatchResult {
                    person,
                    shared_attributes: shared_attrs,
                    match_score,
                    compatibility_analysis: None,
                })
            })
            .collect();
        
        // Sort by match score
        results.sort_by(|a, b| b.match_score.partial_cmp(&a.match_score).unwrap_or(std::cmp::Ordering::Equal));
        
        results
    }
    
    /// Calculate attribute match score
    fn calculate_attribute_match_score(&self, shared_attrs: &[SharedAttributeDetail]) -> f64 {
        if shared_attrs.is_empty() {
            return 0.0;
        }
        
        // Weighted sum of attribute matches
        let total_weight: f64 = shared_attrs.iter().map(|a| a.weight).sum();
        let max_possible_weight = shared_attrs.len() as f64; // Assuming max weight is 1.0
        
        (total_weight / max_possible_weight).min(1.0)
    }
    
    // ========================================================================
    // Specific Attribute Queries
    // ========================================================================
    
    /// Find people with a specific attribute value
    pub fn find_people_by_attribute(&self, category: &str, value: &str) -> Vec<Entity> {
        let all_attrs = self.store.list_attributes(Some(category));
        
        let matching_attr = all_attrs.into_iter()
            .find(|attr| attr.name.to_lowercase() == value.to_lowercase());
        
        if let Some(attr) = matching_attr {
            self.get_persons_by_attribute(&attr.id)
        } else {
            vec![]
        }
    }
    
    /// Find alumni (people who went to the same school)
    pub fn find_alumni(&self, person_id: &str) -> Vec<Entity> {
        let schools = self.get_person_attributes(person_id, Some("school"));
        
        let mut alumni: HashSet<String> = HashSet::new();
        
        for school in schools {
            for person in self.get_persons_by_attribute(&school.id) {
                if person.id != person_id {
                    alumni.insert(person.id);
                }
            }
        }
        
        alumni.into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    /// Find people in the same city
    pub fn find_same_city(&self, person_id: &str) -> Vec<Entity> {
        let cities = self.get_person_attributes(person_id, Some("city"));
        let locations = self.get_person_attributes(person_id, Some("location"));
        
        let mut same_city: HashSet<String> = HashSet::new();
        
        for attr in cities.into_iter().chain(locations) {
            for person in self.get_persons_by_attribute(&attr.id) {
                if person.id != person_id {
                    same_city.insert(person.id);
                }
            }
        }
        
        same_city.into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    /// Find people with the same zodiac sign
    pub fn find_same_zodiac(&self, person_id: &str) -> Vec<Entity> {
        let zodiacs = self.get_person_attributes(person_id, Some("zodiac"));
        
        let mut same_zodiac: HashSet<String> = HashSet::new();
        
        for zodiac in zodiacs {
            for person in self.get_persons_by_attribute(&zodiac.id) {
                if person.id != person_id {
                    same_zodiac.insert(person.id);
                }
            }
        }
        
        same_zodiac.into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    /// Find people with shared hobbies
    pub fn find_same_hobbies(&self, person_id: &str) -> Vec<AttributeMatchResult> {
        self.find_people_by_shared_attributes(person_id, Some(AttributeMatchConfig {
            include_categories: Some(vec!["hobby".to_string()]),
            ..AttributeMatchConfig::default()
        }))
    }
    
    // ========================================================================
    // Attribute Similarity
    // ========================================================================
    
    /// Calculate attribute similarity between two people
    pub fn calculate_attribute_similarity(
        &self,
        person_a: &str,
        person_b: &str,
        config: Option<AttributeMatchConfig>,
    ) -> f64 {
        let cfg = config.unwrap_or_default();
        
        let attrs_a = self.store.get_person_attributes(person_a);
        let attrs_b = self.store.get_person_attributes(person_b);
        
        if attrs_a.is_empty() || attrs_b.is_empty() {
            return 0.0;
        }
        
        let shared = self.find_shared_attributes(person_a, person_b);
        
        // Calculate weighted Jaccard similarity
        let shared_weight: f64 = shared.iter().map(|s| s.weight).sum();
        
        let all_weight_a: f64 = attrs_a.iter()
            .map(|a| {
                let cat = a.properties.get("category")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                cfg.category_weights.get(cat).copied().unwrap_or(0.5)
            })
            .sum();
        
        let all_weight_b: f64 = attrs_b.iter()
            .map(|a| {
                let cat = a.properties.get("category")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                cfg.category_weights.get(cat).copied().unwrap_or(0.5)
            })
            .sum();
        
        let union_weight = all_weight_a + all_weight_b - shared_weight;
        
        if union_weight > 0.0 {
            shared_weight / union_weight
        } else {
            0.0
        }
    }
    
    // ========================================================================
    // Compatibility Analysis
    // ========================================================================
    
    /// Analyze compatibility between two people
    pub fn analyze_compatibility(&self, person_a: &str, person_b: &str) -> CompatibilityResult {
        let shared = self.find_shared_attributes(person_a, person_b);
        
        // Calculate overall score
        let overall_score = self.calculate_attribute_similarity(person_a, person_b, None);
        
        // Find complementary attributes
        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();
        
        let unique_to_a: Vec<String> = attrs_a.difference(&attrs_b)
            .filter_map(|id| self.store.get_attribute(id))
            .map(|a| a.name)
            .take(3)
            .collect();
        
        let unique_to_b: Vec<String> = attrs_b.difference(&attrs_a)
            .filter_map(|id| self.store.get_attribute(id))
            .map(|a| a.name)
            .take(3)
            .collect();
        
        let mut complementary: Vec<String> = Vec::new();
        if !unique_to_a.is_empty() || !unique_to_b.is_empty() {
            complementary.push(format!("互补属性: {:?} / {:?}", unique_to_a, unique_to_b));
        }
        
        // Generate analysis text
        let analysis = self.generate_compatibility_analysis(&shared, overall_score);
        
        CompatibilityResult {
            overall_score,
            shared_attributes: shared,
            complementary_attributes: complementary,
            analysis,
        }
    }
    
    /// Generate compatibility analysis text
    fn generate_compatibility_analysis(
        &self,
        shared: &[SharedAttributeDetail],
        score: f64,
    ) -> String {
        if shared.is_empty() {
            return "没有发现共同属性".to_string();
        }
        
        let mut parts: Vec<String> = Vec::new();
        
        // Group by category
        let mut by_category: HashMap<String, Vec<String>> = HashMap::new();
        for attr in shared {
            by_category.entry(attr.category.clone())
                .or_default()
                .push(attr.attribute.name.clone());
        }
        
        for (category, values) in by_category {
            let desc = match category.as_str() {
                "school" | "education" => format!("校友 ({})", values.join(", ")),
                "city" | "location" => format!("同城 ({})", values.join(", ")),
                "zodiac" => format!("同星座 ({})", values.join(", ")),
                "hobby" => format!("共同爱好 ({})", values.join(", ")),
                "industry" => format!("同行业 ({})", values.join(", ")),
                "mbti" => format!("同MBTI ({})", values.join(", ")),
                _ => format!("{}: {}", category, values.join(", ")),
            };
            parts.push(desc);
        }
        
        let score_desc = if score >= 0.7 {
            "高度相似"
        } else if score >= 0.4 {
            "中度相似"
        } else {
            "有一些共同点"
        };
        
        format!("{}。{}", parts.join("，"), score_desc)
    }
    
    // ========================================================================
    // Popular Attributes
    // ========================================================================
    
    /// Get popular attributes in a category
    pub fn get_popular_attributes(
        &self,
        category: &str,
        limit: usize,
    ) -> Vec<SharedAttributeResult> {
        let all_attrs = self.store.list_attributes(Some(category));
        
        let mut results: Vec<SharedAttributeResult> = all_attrs.into_iter()
            .map(|attr| {
                let persons = self.get_persons_by_attribute(&attr.id);
                let person_count = persons.len();
                SharedAttributeResult {
                    attribute: attr,
                    category: category.to_string(),
                    persons,
                    person_count,
                }
            })
            .filter(|r| r.person_count > 0)
            .collect();
        
        // Sort by person count descending
        results.sort_by(|a, b| b.person_count.cmp(&a.person_count));
        results.truncate(limit);
        
        results
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use super::super::store_v2::SocialStoreV2Config;
    use super::super::types::PersonInput;
    
    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_shared_attributes() {
        let store = create_test_store();
        
        let alice = store.add_person(PersonInput {
            id: None, name: "Alice".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let bob = store.add_person(PersonInput {
            id: None, name: "Bob".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        let leo = store.find_or_create_attribute("zodiac", "Leo");
        
        store.set_person_attribute(&alice.id, &leo.id, None);
        store.set_person_attribute(&bob.id, &leo.id, None);
        
        let matcher = AttributeMatcher::new(&store);
        let shared = matcher.find_shared_attributes(&alice.id, &bob.id);
        
        assert_eq!(shared.len(), 1);
        assert_eq!(shared[0].attribute.name, "Leo");
        assert_eq!(shared[0].category, "zodiac");
    }
    
    #[test]
    fn test_find_alumni() {
        let store = create_test_store();
        
        let alice = store.add_person(PersonInput {
            id: None, name: "Alice".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let bob = store.add_person(PersonInput {
            id: None, name: "Bob".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let charlie = store.add_person(PersonInput {
            id: None, name: "Charlie".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        let tsinghua = store.find_or_create_attribute("school", "Tsinghua University");
        let mit = store.find_or_create_attribute("school", "MIT");
        
        store.set_person_attribute(&alice.id, &tsinghua.id, None);
        store.set_person_attribute(&bob.id, &tsinghua.id, None);
        store.set_person_attribute(&charlie.id, &mit.id, None);
        
        let matcher = AttributeMatcher::new(&store);
        let alumni = matcher.find_alumni(&alice.id);
        
        assert_eq!(alumni.len(), 1);
        assert_eq!(alumni[0].name, "Bob");
    }
    
    #[test]
    fn test_find_same_city() {
        let store = create_test_store();
        
        let alice = store.add_person(PersonInput {
            id: None, name: "Alice".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let bob = store.add_person(PersonInput {
            id: None, name: "Bob".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        let beijing = store.find_or_create_attribute("city", "Beijing");
        
        store.set_person_attribute(&alice.id, &beijing.id, None);
        store.set_person_attribute(&bob.id, &beijing.id, None);
        
        let matcher = AttributeMatcher::new(&store);
        let same_city = matcher.find_same_city(&alice.id);
        
        assert_eq!(same_city.len(), 1);
        assert_eq!(same_city[0].name, "Bob");
    }
    
    #[test]
    fn test_analyze_compatibility() {
        let store = create_test_store();
        
        let alice = store.add_person(PersonInput {
            id: None, name: "Alice".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let bob = store.add_person(PersonInput {
            id: None, name: "Bob".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        let leo = store.find_or_create_attribute("zodiac", "Leo");
        let beijing = store.find_or_create_attribute("city", "Beijing");
        let photography = store.find_or_create_attribute("hobby", "Photography");
        
        store.set_person_attribute(&alice.id, &leo.id, None);
        store.set_person_attribute(&alice.id, &beijing.id, None);
        store.set_person_attribute(&alice.id, &photography.id, None);
        
        store.set_person_attribute(&bob.id, &leo.id, None);
        store.set_person_attribute(&bob.id, &beijing.id, None);
        
        let matcher = AttributeMatcher::new(&store);
        let result = matcher.analyze_compatibility(&alice.id, &bob.id);
        
        assert!(result.overall_score > 0.0);
        assert_eq!(result.shared_attributes.len(), 2); // Leo and Beijing
        assert!(!result.analysis.is_empty());
    }
    
    #[test]
    fn test_find_people_by_shared_attributes() {
        let store = create_test_store();
        
        let alice = store.add_person(PersonInput {
            id: None, name: "Alice".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        let bob = store.add_person(PersonInput {
            id: None, name: "Bob".to_string(),
            avatar: None, description: None, phone: None, email: None, wechat: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        let tsinghua = store.find_or_create_attribute("school", "Tsinghua");
        let beijing = store.find_or_create_attribute("city", "Beijing");
        
        store.set_person_attribute(&alice.id, &tsinghua.id, None);
        store.set_person_attribute(&alice.id, &beijing.id, None);
        store.set_person_attribute(&bob.id, &tsinghua.id, None);
        store.set_person_attribute(&bob.id, &beijing.id, None);
        
        let matcher = AttributeMatcher::new(&store);
        let results = matcher.find_people_by_shared_attributes(&alice.id, None);
        
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].person.name, "Bob");
        assert_eq!(results[0].shared_attributes.len(), 2);
    }
}
