//! Birthday matching module for social graph
//!
//! Provides birthday-based matching and discovery:
//! - Find people with same birthday (exact: same year-month-day)
//! - Find people with same month-day birthday (different years)
//! - Birthday recommendations
//! - Upcoming birthday queries

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

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

/// Birthday match type
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BirthdayMatchType {
    /// Same year, month, and day (exact match)
    ExactMatch,
    /// Same month and day (different year)
    SameDayMonth,
    /// Same month only
    SameMonth,
}

impl BirthdayMatchType {
    pub fn as_str(&self) -> &'static str {
        match self {
            BirthdayMatchType::ExactMatch => "exact",
            BirthdayMatchType::SameDayMonth => "same_day_month",
            BirthdayMatchType::SameMonth => "same_month",
        }
    }
}

/// Birthday match result
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BirthdayMatchResult {
    /// The person entity
    pub person: Entity,
    /// Birthday in YYYY-MM-DD format
    pub birthday: String,
    /// Type of match
    pub match_type: BirthdayMatchType,
}

/// Birthday statistics
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BirthdayStats {
    /// Total number of people with birthdays set
    pub total_with_birthday: usize,
    /// Most common month-day birthdays: (MM-DD, count)
    pub most_common_dates: Vec<(String, usize)>,
    /// Count by month (1-12)
    pub by_month: HashMap<u8, usize>,
}

/// Birthday index for efficient queries
#[derive(Debug, Clone, Default)]
pub struct BirthdayIndex {
    /// Full date index: YYYY-MM-DD -> Vec<person_id>
    by_full_date: HashMap<String, Vec<String>>,
    /// Month-day index: MM-DD -> Vec<person_id>
    by_month_day: HashMap<String, Vec<String>>,
    /// Month index: month (1-12) -> Vec<person_id>
    by_month: HashMap<u8, Vec<String>>,
    /// Person to birthday mapping: person_id -> YYYY-MM-DD
    person_birthday: HashMap<String, String>,
}

impl BirthdayIndex {
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a birthday to the index
    pub fn add(&mut self, person_id: &str, birthday: &str) {
        // Validate and parse birthday
        if let Some((year, month, day)) = parse_birthday(birthday) {
            let full_date = format!("{:04}-{:02}-{:02}", year, month, day);
            let month_day = format!("{:02}-{:02}", month, day);

            // Remove old entry if exists (clone to avoid borrow conflict)
            if let Some(old_birthday) = self.person_birthday.get(person_id).cloned() {
                self.remove(person_id, &old_birthday);
            }

            // Add to full date index
            self.by_full_date
                .entry(full_date.clone())
                .or_default()
                .push(person_id.to_string());

            // Add to month-day index
            self.by_month_day
                .entry(month_day)
                .or_default()
                .push(person_id.to_string());

            // Add to month index
            self.by_month
                .entry(month)
                .or_default()
                .push(person_id.to_string());

            // Store person -> birthday mapping
            self.person_birthday.insert(person_id.to_string(), full_date);
        }
    }

    /// Remove a birthday from the index
    pub fn remove(&mut self, person_id: &str, birthday: &str) {
        if let Some((_, month, _)) = parse_birthday(birthday) {
            let month_day = get_month_day(birthday).unwrap_or_default();

            // Remove from full date index
            if let Some(list) = self.by_full_date.get_mut(birthday) {
                list.retain(|id| id != person_id);
            }

            // Remove from month-day index
            if let Some(list) = self.by_month_day.get_mut(&month_day) {
                list.retain(|id| id != person_id);
            }

            // Remove from month index
            if let Some(list) = self.by_month.get_mut(&month) {
                list.retain(|id| id != person_id);
            }

            // Remove person -> birthday mapping
            self.person_birthday.remove(person_id);
        }
    }

    /// Get people with exact same birthday (YYYY-MM-DD)
    pub fn get_by_full_date(&self, birthday: &str) -> Vec<String> {
        self.by_full_date
            .get(birthday)
            .cloned()
            .unwrap_or_default()
    }

    /// Get people with same month-day birthday (MM-DD)
    pub fn get_by_month_day(&self, month_day: &str) -> Vec<String> {
        self.by_month_day
            .get(month_day)
            .cloned()
            .unwrap_or_default()
    }

    /// Get people with birthdays in a specific month
    pub fn get_by_month(&self, month: u8) -> Vec<String> {
        self.by_month.get(&month).cloned().unwrap_or_default()
    }

    /// Get a person's birthday
    pub fn get_person_birthday(&self, person_id: &str) -> Option<String> {
        self.person_birthday.get(person_id).cloned()
    }

    /// Get total count of people with birthdays
    pub fn total_count(&self) -> usize {
        self.person_birthday.len()
    }

    /// Get statistics
    pub fn get_stats(&self, limit: usize) -> BirthdayStats {
        let mut most_common: Vec<(String, usize)> = self
            .by_month_day
            .iter()
            .map(|(date, people)| (date.clone(), people.len()))
            .collect();
        most_common.sort_by(|a, b| b.1.cmp(&a.1));
        most_common.truncate(limit);

        let by_month: HashMap<u8, usize> = self
            .by_month
            .iter()
            .map(|(month, people)| (*month, people.len()))
            .collect();

        BirthdayStats {
            total_with_birthday: self.person_birthday.len(),
            most_common_dates: most_common,
            by_month,
        }
    }
}

/// Birthday matcher for finding people with same/similar birthdays
pub struct BirthdayMatcher<'a> {
    store: &'a SocialStoreV2,
}

impl<'a> BirthdayMatcher<'a> {
    pub fn new(store: &'a SocialStoreV2) -> Self {
        Self { store }
    }

    /// Find people with exact same birthday (same year-month-day)
    pub fn find_same_birthday_exact(&self, birthday: &str) -> Vec<Entity> {
        let index = self.store.birthday_index();
        let person_ids = index.get_by_full_date(birthday);
        self.get_persons_by_ids(&person_ids)
    }

    /// Find people with same month-day birthday (different years allowed)
    pub fn find_same_birthday_day_month(&self, birthday: &str) -> Vec<Entity> {
        let month_day = get_month_day(birthday).unwrap_or_else(|| birthday.to_string());
        let index = self.store.birthday_index();
        let person_ids = index.get_by_month_day(&month_day);
        self.get_persons_by_ids(&person_ids)
    }

    /// Find people with same birthday as a given person (exact match)
    pub fn find_same_birthday_as_person_exact(&self, person_id: &str) -> Vec<Entity> {
        let index = self.store.birthday_index();
        if let Some(birthday) = index.get_person_birthday(person_id) {
            let mut results = self.find_same_birthday_exact(&birthday);
            // Remove the person themselves from results
            results.retain(|e| e.id != person_id);
            results
        } else {
            Vec::new()
        }
    }

    /// Find people with same month-day birthday as a given person
    pub fn find_same_birthday_as_person(&self, person_id: &str) -> Vec<Entity> {
        let index = self.store.birthday_index();
        if let Some(birthday) = index.get_person_birthday(person_id) {
            let mut results = self.find_same_birthday_day_month(&birthday);
            // Remove the person themselves from results
            results.retain(|e| e.id != person_id);
            results
        } else {
            Vec::new()
        }
    }

    /// Recommend birthday matches with priority: exact > same_day_month
    pub fn recommend_birthday_matches(
        &self,
        person_id: &str,
        limit: usize,
    ) -> Vec<BirthdayMatchResult> {
        let index = self.store.birthday_index();
        let birthday = match index.get_person_birthday(person_id) {
            Some(b) => b,
            None => return Vec::new(),
        };

        let mut results = Vec::new();

        // First, add exact matches (same year-month-day)
        let exact_ids = index.get_by_full_date(&birthday);
        for id in exact_ids {
            if id != person_id {
                if let Some(person) = self.store.get_person(&id) {
                    results.push(BirthdayMatchResult {
                        person,
                        birthday: birthday.clone(),
                        match_type: BirthdayMatchType::ExactMatch,
                    });
                }
            }
        }

        // Then, add same month-day matches (different years)
        if let Some(month_day) = get_month_day(&birthday) {
            let month_day_ids = index.get_by_month_day(&month_day);
            for id in month_day_ids {
                if id != person_id {
                    // Skip if already added as exact match
                    if results.iter().any(|r| r.person.id == id) {
                        continue;
                    }
                    if let Some(person) = self.store.get_person(&id) {
                        if let Some(their_birthday) = index.get_person_birthday(&id) {
                            results.push(BirthdayMatchResult {
                                person,
                                birthday: their_birthday,
                                match_type: BirthdayMatchType::SameDayMonth,
                            });
                        }
                    }
                }
            }
        }

        results.truncate(limit);
        results
    }

    /// Find people with birthdays in the next N days
    pub fn find_upcoming_birthdays(&self, days: u32) -> Vec<BirthdayMatchResult> {
        use std::time::{SystemTime, UNIX_EPOCH};
        
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        // Calculate current date
        let days_since_epoch = now / 86400;
        let _current_year = 1970 + (days_since_epoch / 365) as u16; // Approximate
        
        // Get current month and day (simplified calculation)
        let day_of_year = (days_since_epoch % 365) as u16;
        let (current_month, current_day) = day_of_year_to_month_day(day_of_year);

        let index = self.store.birthday_index();
        let mut results = Vec::new();

        // Check each day in the range
        for offset in 0..=days {
            let (check_month, check_day) = add_days_to_date(current_month, current_day, offset);
            let month_day = format!("{:02}-{:02}", check_month, check_day);

            let person_ids = index.get_by_month_day(&month_day);
            for id in person_ids {
                if let Some(person) = self.store.get_person(&id) {
                    if let Some(birthday) = index.get_person_birthday(&id) {
                        results.push(BirthdayMatchResult {
                            person,
                            birthday,
                            match_type: if offset == 0 {
                                BirthdayMatchType::ExactMatch
                            } else {
                                BirthdayMatchType::SameDayMonth
                            },
                        });
                    }
                }
            }
        }

        results
    }

    /// Find people with birthdays today
    pub fn find_today_birthdays(&self) -> Vec<Entity> {
        use std::time::{SystemTime, UNIX_EPOCH};
        
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        
        let days_since_epoch = now / 86400;
        let day_of_year = (days_since_epoch % 365) as u16;
        let (month, day) = day_of_year_to_month_day(day_of_year);
        
        let month_day = format!("{:02}-{:02}", month, day);
        let index = self.store.birthday_index();
        let person_ids = index.get_by_month_day(&month_day);
        self.get_persons_by_ids(&person_ids)
    }

    /// Find people with birthdays in a specific month
    pub fn find_birthdays_in_month(&self, month: u8) -> Vec<Entity> {
        if month < 1 || month > 12 {
            return Vec::new();
        }
        let index = self.store.birthday_index();
        let person_ids = index.get_by_month(month);
        self.get_persons_by_ids(&person_ids)
    }

    /// Get birthday statistics
    pub fn get_birthday_stats(&self) -> BirthdayStats {
        self.store.birthday_index().get_stats(10)
    }

    /// Check if two people have the same birthday
    pub fn is_same_birthday(&self, person_a: &str, person_b: &str, exact: bool) -> bool {
        let index = self.store.birthday_index();
        let birthday_a = match index.get_person_birthday(person_a) {
            Some(b) => b,
            None => return false,
        };
        let birthday_b = match index.get_person_birthday(person_b) {
            Some(b) => b,
            None => return false,
        };

        if exact {
            birthday_a == birthday_b
        } else {
            get_month_day(&birthday_a) == get_month_day(&birthday_b)
        }
    }

    /// Helper: Get person entities by IDs
    fn get_persons_by_ids(&self, ids: &[String]) -> Vec<Entity> {
        ids.iter()
            .filter_map(|id| self.store.get_person(id))
            .collect()
    }
}

/// Parse birthday string (YYYY-MM-DD) into (year, month, day)
pub fn parse_birthday(birthday: &str) -> Option<(u16, u8, u8)> {
    let parts: Vec<&str> = birthday.split('-').collect();
    if parts.len() != 3 {
        return None;
    }

    let year: u16 = parts[0].parse().ok()?;
    let month: u8 = parts[1].parse().ok()?;
    let day: u8 = parts[2].parse().ok()?;

    // Validate ranges
    if year < 1900 || year > 2100 {
        return None;
    }
    if month < 1 || month > 12 {
        return None;
    }
    if day < 1 || day > 31 {
        return None;
    }

    // Validate day for month
    let max_day = match month {
        1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
        4 | 6 | 9 | 11 => 30,
        2 => {
            if is_leap_year(year) {
                29
            } else {
                28
            }
        }
        _ => return None,
    };

    if day > max_day {
        return None;
    }

    Some((year, month, day))
}

/// Get month-day string (MM-DD) from full birthday (YYYY-MM-DD)
pub fn get_month_day(birthday: &str) -> Option<String> {
    let parts: Vec<&str> = birthday.split('-').collect();
    if parts.len() == 3 {
        Some(format!("{}-{}", parts[1], parts[2]))
    } else if parts.len() == 2 {
        // Already MM-DD format
        Some(birthday.to_string())
    } else {
        None
    }
}

/// Check if a year is a leap year
fn is_leap_year(year: u16) -> bool {
    (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)
}

/// Convert day of year to (month, day) - simplified
fn day_of_year_to_month_day(day_of_year: u16) -> (u8, u8) {
    let days_in_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    let mut remaining = day_of_year;
    
    for (i, &days) in days_in_months.iter().enumerate() {
        if remaining < days as u16 {
            return ((i + 1) as u8, (remaining + 1) as u8);
        }
        remaining -= days as u16;
    }
    
    (12, 31) // Fallback to Dec 31
}

/// Add days to a date (simplified, doesn't handle year boundary perfectly)
fn add_days_to_date(month: u8, day: u8, days_to_add: u32) -> (u8, u8) {
    let days_in_months = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    let mut m = month as usize;
    let mut d = day as u32 + days_to_add;
    
    while d > days_in_months[m - 1] {
        d -= days_in_months[m - 1];
        m += 1;
        if m > 12 {
            m = 1;
        }
    }
    
    (m as u8, d as u8)
}

/// Validate birthday format (YYYY-MM-DD)
pub fn validate_birthday(birthday: &str) -> bool {
    parse_birthday(birthday).is_some()
}

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

    #[test]
    fn test_parse_birthday() {
        assert_eq!(parse_birthday("1990-05-15"), Some((1990, 5, 15)));
        assert_eq!(parse_birthday("2000-02-29"), Some((2000, 2, 29))); // Leap year
        assert_eq!(parse_birthday("1999-02-29"), None); // Not a leap year
        assert_eq!(parse_birthday("1990-13-01"), None); // Invalid month
        assert_eq!(parse_birthday("1990-04-31"), None); // April has 30 days
        assert_eq!(parse_birthday("invalid"), None);
    }

    #[test]
    fn test_get_month_day() {
        assert_eq!(get_month_day("1990-05-15"), Some("05-15".to_string()));
        assert_eq!(get_month_day("05-15"), Some("05-15".to_string()));
        assert_eq!(get_month_day("invalid"), None);
    }

    #[test]
    fn test_birthday_index() {
        let mut index = BirthdayIndex::new();
        
        index.add("alice", "1990-05-15");
        index.add("bob", "1992-05-15");
        index.add("charlie", "1990-05-15");
        
        // Test full date lookup
        let same_exact = index.get_by_full_date("1990-05-15");
        assert_eq!(same_exact.len(), 2);
        assert!(same_exact.contains(&"alice".to_string()));
        assert!(same_exact.contains(&"charlie".to_string()));
        
        // Test month-day lookup
        let same_day = index.get_by_month_day("05-15");
        assert_eq!(same_day.len(), 3);
        
        // Test month lookup
        let may_birthdays = index.get_by_month(5);
        assert_eq!(may_birthdays.len(), 3);
    }

    #[test]
    fn test_is_leap_year() {
        assert!(is_leap_year(2000)); // Divisible by 400
        assert!(is_leap_year(2024)); // Divisible by 4, not 100
        assert!(!is_leap_year(1900)); // Divisible by 100, not 400
        assert!(!is_leap_year(2023)); // Not divisible by 4
    }
}
