//! Item Matcher - Shared item discovery and matching
//!
//! Provides algorithms for finding connections through shared items:
//! - Shared item discovery between people
//! - Finding people by items they own
//! - Item similarity matching
//! - Popular items discovery

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

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

/// Item matcher for shared item discovery
pub struct ItemMatcher<'a> {
    store: &'a SocialStoreV2,
}

impl<'a> ItemMatcher<'a> {
    /// Create a new ItemMatcher
    pub fn new(store: &'a SocialStoreV2) -> Self {
        Self { store }
    }
    
    // ========================================================================
    // Basic Item Queries
    // ========================================================================
    
    /// Get all items owned by a person
    pub fn get_person_items(&self, person_id: &str, category: Option<&str>) -> Vec<Entity> {
        let items = self.store.get_person_items(person_id);
        
        if let Some(cat) = category {
            items.into_iter()
                .filter(|item| {
                    item.properties.get("category")
                        .and_then(|v| v.as_str())
                        .map(|c| c == cat)
                        .unwrap_or(false)
                })
                .collect()
        } else {
            items
        }
    }
    
    /// Get all owners of an item
    pub fn get_item_owners(&self, item_id: &str) -> Vec<Entity> {
        self.store.get_item_owners(item_id)
    }
    
    // ========================================================================
    // Shared Item Discovery
    // ========================================================================
    
    /// Find shared items between two people
    pub fn find_shared_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()
    }
    
    /// Find people who share items with a given person
    pub fn find_people_by_shared_items(
        &self,
        person_id: &str,
        category: Option<&str>,
    ) -> Vec<ItemMatchResult> {
        let my_items = self.get_person_items(person_id, category);
        
        if my_items.is_empty() {
            return vec![];
        }
        
        // Track candidates and their shared items
        let mut candidates: HashMap<String, Vec<Entity>> = HashMap::new();
        
        for item in &my_items {
            let owners = self.get_item_owners(&item.id);
            for owner in owners {
                if owner.id != person_id {
                    candidates.entry(owner.id.clone())
                        .or_default()
                        .push(item.clone());
                }
            }
        }
        
        // Build results
        let mut results: Vec<ItemMatchResult> = candidates.into_iter()
            .filter_map(|(owner_id, shared_items)| {
                let person = self.store.get_person(&owner_id)?;
                let match_score = self.calculate_item_match_score(&my_items, &shared_items);
                let match_details = shared_items.iter()
                    .map(|item| ItemMatchDetail {
                        item: item.clone(),
                        relation_type: "owns".to_string(),
                    })
                    .collect();
                
                Some(ItemMatchResult {
                    person,
                    shared_items,
                    match_score,
                    match_details,
                })
            })
            .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 item match score
    fn calculate_item_match_score(&self, my_items: &[Entity], shared_items: &[Entity]) -> f64 {
        if my_items.is_empty() {
            return 0.0;
        }
        
        // Base score from number of shared items
        let base_score = (shared_items.len() as f64 / my_items.len() as f64).min(1.0);
        
        // Bonus for high-value matches (same brand, same model)
        let mut bonus = 0.0;
        for item in shared_items {
            // Items with brand get a small bonus
            if item.properties.contains_key("brand") {
                bonus += 0.05;
            }
            // Items with model (more specific) get a larger bonus
            if item.properties.contains_key("model") {
                bonus += 0.1;
            }
        }
        
        (base_score + bonus).min(1.0)
    }
    
    // ========================================================================
    // Item-based People Discovery
    // ========================================================================
    
    /// Find people who own a specific item
    pub fn find_people_by_item(
        &self,
        item_name: &str,
        brand: Option<&str>,
        model: Option<&str>,
    ) -> Vec<Entity> {
        let all_items = self.store.list_items(None);
        
        let matching_items: Vec<&Entity> = all_items.iter()
            .filter(|item| {
                // Match name (partial match)
                let name_matches = item.name.to_lowercase().contains(&item_name.to_lowercase());
                
                // Match brand if specified
                let brand_matches = brand.map(|b| {
                    item.properties.get("brand")
                        .and_then(|v| v.as_str())
                        .map(|ib| ib.to_lowercase() == b.to_lowercase())
                        .unwrap_or(false)
                }).unwrap_or(true);
                
                // Match model if specified
                let model_matches = model.map(|m| {
                    item.properties.get("model")
                        .and_then(|v| v.as_str())
                        .map(|im| im.to_lowercase().contains(&m.to_lowercase()))
                        .unwrap_or(false)
                }).unwrap_or(true);
                
                name_matches && brand_matches && model_matches
            })
            .collect();
        
        // Get all owners of matching items
        let mut owners: HashSet<String> = HashSet::new();
        for item in matching_items {
            for owner in self.get_item_owners(&item.id) {
                owners.insert(owner.id);
            }
        }
        
        owners.into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    /// Find people who own items of a specific brand
    pub fn find_people_by_brand(&self, brand: &str, category: Option<&str>) -> Vec<Entity> {
        let all_items = self.store.list_items(category);
        
        let matching_items: Vec<&Entity> = all_items.iter()
            .filter(|item| {
                item.properties.get("brand")
                    .and_then(|v| v.as_str())
                    .map(|b| b.to_lowercase() == brand.to_lowercase())
                    .unwrap_or(false)
            })
            .collect();
        
        let mut owners: HashSet<String> = HashSet::new();
        for item in matching_items {
            for owner in self.get_item_owners(&item.id) {
                owners.insert(owner.id);
            }
        }
        
        owners.into_iter()
            .filter_map(|id| self.store.get_person(&id))
            .collect()
    }
    
    // ========================================================================
    // Item Similarity
    // ========================================================================
    
    /// Calculate item similarity between two people
    pub fn calculate_item_similarity(&self, person_a: &str, person_b: &str) -> f64 {
        let items_a = self.store.get_person_items(person_a);
        let items_b = self.store.get_person_items(person_b);
        
        if items_a.is_empty() || items_b.is_empty() {
            return 0.0;
        }
        
        let shared = self.find_shared_items(person_a, person_b);
        
        // Jaccard similarity
        let union_size = items_a.len() + items_b.len() - shared.len();
        if union_size == 0 {
            return 0.0;
        }
        
        let jaccard = shared.len() as f64 / union_size as f64;
        
        // Brand similarity bonus
        let brands_a: HashSet<String> = items_a.iter()
            .filter_map(|i| i.properties.get("brand")?.as_str().map(|s| s.to_lowercase()))
            .collect();
        
        let brands_b: HashSet<String> = items_b.iter()
            .filter_map(|i| i.properties.get("brand")?.as_str().map(|s| s.to_lowercase()))
            .collect();
        
        let shared_brands = brands_a.intersection(&brands_b).count();
        let brand_bonus = if !brands_a.is_empty() && !brands_b.is_empty() {
            shared_brands as f64 / brands_a.union(&brands_b).count() as f64 * 0.2
        } else {
            0.0
        };
        
        (jaccard + brand_bonus).min(1.0)
    }
    
    // ========================================================================
    // Popular Items
    // ========================================================================
    
    /// Get popular items (most owned)
    pub fn get_popular_items(&self, category: Option<&str>, limit: usize) -> Vec<SharedItemResult> {
        let all_items = self.store.list_items(category);
        
        let mut results: Vec<SharedItemResult> = all_items.into_iter()
            .map(|item| {
                let owners = self.get_item_owners(&item.id);
                let owner_count = owners.len();
                SharedItemResult {
                    item,
                    owners,
                    owner_count,
                }
            })
            .filter(|r| r.owner_count > 0)
            .collect();
        
        // Sort by owner count descending
        results.sort_by(|a, b| b.owner_count.cmp(&a.owner_count));
        results.truncate(limit);
        
        results
    }
    
    /// Get items shared by multiple people
    pub fn get_commonly_shared_items(&self, min_owners: usize) -> Vec<SharedItemResult> {
        let all_items = self.store.list_items(None);
        
        all_items.into_iter()
            .filter_map(|item| {
                let owners = self.get_item_owners(&item.id);
                if owners.len() >= min_owners {
                    Some(SharedItemResult {
                        item,
                        owner_count: owners.len(),
                        owners,
                    })
                } else {
                    None
                }
            })
            .collect()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;
    use super::super::store_v2::SocialStoreV2Config;
    use super::super::types::{PersonInput, ItemInput};
    
    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_items() {
        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 tesla = store.add_item(ItemInput {
            id: None,
            name: "Tesla Model Y".to_string(),
            category: "vehicle".to_string(),
            brand: Some("Tesla".to_string()),
            model: Some("Model Y".to_string()),
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_item_to_person(&alice.id, &tesla.id, None);
        store.add_item_to_person(&bob.id, &tesla.id, None);
        
        let matcher = ItemMatcher::new(&store);
        let shared = matcher.find_shared_items(&alice.id, &bob.id);
        
        assert_eq!(shared.len(), 1);
        assert_eq!(shared[0].name, "Tesla Model Y");
    }
    
    #[test]
    fn test_find_people_by_shared_items() {
        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 iphone = store.add_item(ItemInput {
            id: None,
            name: "iPhone 15 Pro".to_string(),
            category: "phone".to_string(),
            brand: Some("Apple".to_string()),
            model: Some("iPhone 15 Pro".to_string()),
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_item_to_person(&alice.id, &iphone.id, None);
        store.add_item_to_person(&bob.id, &iphone.id, None);
        
        let matcher = ItemMatcher::new(&store);
        let results = matcher.find_people_by_shared_items(&alice.id, None);
        
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].person.name, "Bob");
        assert_eq!(results[0].shared_items.len(), 1);
    }
    
    #[test]
    fn test_find_people_by_brand() {
        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 tesla_y = store.add_item(ItemInput {
            id: None,
            name: "Tesla Model Y".to_string(),
            category: "vehicle".to_string(),
            brand: Some("Tesla".to_string()),
            model: Some("Model Y".to_string()),
            properties: std::collections::BTreeMap::new(),
        });
        
        let tesla_3 = store.add_item(ItemInput {
            id: None,
            name: "Tesla Model 3".to_string(),
            category: "vehicle".to_string(),
            brand: Some("Tesla".to_string()),
            model: Some("Model 3".to_string()),
            properties: std::collections::BTreeMap::new(),
        });
        
        store.add_item_to_person(&alice.id, &tesla_y.id, None);
        store.add_item_to_person(&bob.id, &tesla_3.id, None);
        
        let matcher = ItemMatcher::new(&store);
        let tesla_owners = matcher.find_people_by_brand("Tesla", None);
        
        assert_eq!(tesla_owners.len(), 2);
    }
    
    #[test]
    fn test_popular_items() {
        let store = create_test_store();
        
        let persons: Vec<_> = (0..3).map(|i| {
            store.add_person(PersonInput {
                id: None, name: format!("Person{}", i),
                avatar: None, description: None, phone: None, email: None, wechat: None,
                properties: std::collections::BTreeMap::new(),
            })
        }).collect();
        
        let iphone = store.add_item(ItemInput {
            id: None,
            name: "iPhone".to_string(),
            category: "phone".to_string(),
            brand: Some("Apple".to_string()),
            model: None,
            properties: std::collections::BTreeMap::new(),
        });
        
        // All 3 persons own iPhone
        for person in &persons {
            store.add_item_to_person(&person.id, &iphone.id, None);
        }
        
        let matcher = ItemMatcher::new(&store);
        let popular = matcher.get_popular_items(None, 10);
        
        assert!(!popular.is_empty());
        assert_eq!(popular[0].owner_count, 3);
    }
}
