use crate::{get_type_emoji, is_weak_password};
use chamber_vault::Vault;
use time::{Duration, OffsetDateTime};
#[allow(clippy::too_many_lines)]
pub fn handle_stats_command(vault: &Vault) -> color_eyre::Result<()> {
let items = vault.list_items()?;
let vault_name = match vault.get_vault_id() {
Ok(Some(id)) => format!(" ({id})"),
Ok(None) => " (Default Vault)".to_string(),
Err(_) => String::new(),
};
if items.is_empty() {
println!("📊 Vault Statistics{vault_name}");
println!("══════════════════");
println!("No items found in vault.");
return Ok(());
}
let mut type_counts = std::collections::HashMap::new();
let mut weak_passwords = Vec::new();
let mut old_items = Vec::new();
let mut recently_updated = Vec::new();
let mut duplicate_names = Vec::new();
let mut password_lengths = Vec::new();
let now = OffsetDateTime::now_utc();
let cutoff_date = now - Duration::days(180);
let recent_cutoff = now - Duration::days(30);
let mut name_counts = std::collections::HashMap::new();
for item in &items {
let count = name_counts.entry(&item.name).or_insert(0);
*count += 1;
}
for (name, count) in &name_counts {
if *count > 1 {
duplicate_names.push((name.as_str(), *count));
}
}
for item in &items {
let count = type_counts.entry(item.kind).or_insert(0);
*count += 1;
if item.kind == chamber_vault::ItemKind::Password {
password_lengths.push(item.value.len());
if is_weak_password(&item.value) {
weak_passwords.push(&item.name);
}
}
if item.created_at < cutoff_date {
old_items.push((&item.name, item.created_at));
}
if item.updated_at > recent_cutoff {
recently_updated.push((&item.name, item.updated_at));
}
}
println!("📊 Vault Statistics{vault_name}");
println!("══════════════════");
println!();
println!("📍 Vault Info:");
println!("──────────────");
println!(" 📂 Database Path: {}", vault.db_path().display());
println!();
println!("📋 Overview:");
println!("────────────");
println!(" 📊 Total Items: {}", items.len());
if let Some(oldest) = items.iter().min_by_key(|item| item.created_at) {
let vault_age = now - oldest.created_at;
println!(" 📅 Vault Age: {} days (oldest item)", vault_age.whole_days());
}
if let Some(newest) = items.iter().max_by_key(|item| item.updated_at) {
let last_activity = now - newest.updated_at;
println!(" 🕒 Last Activity: {} days ago", last_activity.whole_days());
}
println!(" 🔄 Recently Updated (30 days): {}", recently_updated.len());
println!();
println!("📁 Items by Type:");
println!("─────────────────");
let mut sorted_types: Vec<_> = type_counts.iter().collect();
sorted_types.sort_by(|a, b| b.1.cmp(a.1));
#[allow(clippy::cast_precision_loss)]
for (item_type, count) in sorted_types {
let percentage = (f64::from(*count) / items.len() as f64) * 100.0;
println!(
" {} {}: {} ({:.1}%)",
get_type_emoji(*item_type),
item_type.display_name(),
count,
percentage
);
}
println!();
if !password_lengths.is_empty() {
println!("🔐 Password Security:");
println!("────────────────────");
#[allow(clippy::cast_precision_loss)]
let avg_length = password_lengths.iter().sum::<usize>() as f64 / password_lengths.len() as f64;
let min_length = password_lengths.iter().min().unwrap_or(&0);
let max_length = password_lengths.iter().max().unwrap_or(&0);
println!(" 📏 Average Length: {avg_length:.1} characters");
println!(" 📐 Length Range: {min_length} - {max_length} characters");
let strong_count = password_lengths.len() - weak_passwords.len();
#[allow(clippy::cast_precision_loss)]
let strong_percentage = (strong_count as f64 / password_lengths.len() as f64) * 100.0;
println!(" 💪 Strong Passwords: {strong_count} ({strong_percentage:.1}%)");
println!(
" 🔓 Weak Passwords: {} ({:.1}%)",
weak_passwords.len(),
100.0 - strong_percentage
);
println!();
}
println!("🔓 Weak Passwords:");
println!("──────────────────");
if weak_passwords.is_empty() {
println!(" ✅ No weak passwords found!");
} else {
println!(" ❌ Found {} weak password(s):", weak_passwords.len());
for name in &weak_passwords {
println!(" • {name}");
}
println!(" 💡 Strong passwords should have at least 10 characters with");
println!(" uppercase, lowercase, numbers, and special characters.");
}
println!();
println!("🔍 Data Quality:");
println!("────────────────");
if duplicate_names.is_empty() {
println!(" ✅ No duplicate names found!");
} else {
println!(" ⚠️ Found {} duplicate name(s):", duplicate_names.len());
duplicate_names.sort_by(|a, b| b.1.cmp(&a.1)); for (name, count) in &duplicate_names {
println!(" • '{name}' appears {count} times");
}
}
println!();
println!("⏰ Age Analysis:");
println!("───────────────");
let new_items = items.iter().filter(|item| item.created_at > recent_cutoff).count();
let medium_items = items
.iter()
.filter(|item| item.created_at <= recent_cutoff && item.created_at >= cutoff_date)
.count();
println!(" 🆕 New Items (< 30 days): {new_items}");
println!(" 📅 Medium Age (30-180 days): {medium_items}");
println!(" ⏳ Old Items (> 180 days): {}", old_items.len());
if !old_items.is_empty() {
println!(" ⚠️ Oldest items:");
let mut sorted_old_items = old_items.clone();
sorted_old_items.sort_by(|a, b| a.1.cmp(&b.1));
for (name, created_at) in sorted_old_items.iter().take(5) {
let age = now - *created_at;
println!(" • {} ({} days old)", name, age.whole_days());
}
if sorted_old_items.len() > 5 {
println!(" ... and {} more", sorted_old_items.len() - 5);
}
println!(" 💡 Consider updating or reviewing old items for security.");
}
println!();
if !recently_updated.is_empty() {
println!("🔄 Recent Activity:");
println!("──────────────────");
let mut sorted_recent = recently_updated;
sorted_recent.sort_by(|a, b| b.1.cmp(&a.1));
println!(" 📝 Recently updated items:");
for (name, updated_at) in sorted_recent.iter().take(5) {
let age = now - *updated_at;
println!(" • {} ({} days ago)", name, age.whole_days());
}
if sorted_recent.len() > 5 {
println!(" ... and {} more", sorted_recent.len() - 5);
}
println!();
}
println!("💡 Security Recommendations:");
println!("────────────────────────────");
let mut recommendations = Vec::new();
if !weak_passwords.is_empty() {
recommendations.push(format!("Update {} weak password(s)", weak_passwords.len()));
}
if !old_items.is_empty() {
recommendations.push(format!("Review {} old item(s) (>180 days)", old_items.len()));
}
if !duplicate_names.is_empty() {
recommendations.push(format!("Resolve {} duplicate name(s)", duplicate_names.len()));
}
let very_old_items = items
.iter()
.filter(|item| item.created_at < (now - Duration::days(365)))
.count();
if very_old_items > 0 {
recommendations.push(format!("Consider rotating {very_old_items} very old item(s) (>1 year)"));
}
if recommendations.is_empty() {
println!(" ✅ Your vault looks healthy! No immediate actions needed.");
} else {
for (i, rec) in recommendations.iter().enumerate() {
println!(" {}. {}", i + 1, rec);
}
}
Ok(())
}