use crate::models::user::{UserActivity, UsageStatistics, UserPreferences, Transaction}; use crate::services::user_persistence::UserPersistence; use rust_decimal::Decimal; use chrono::{Utc, Datelike}; use std::collections::HashMap; /// Configuration for UserService #[derive(Debug, Clone)] pub struct UserServiceConfig { pub activity_limit: usize, } impl Default for UserServiceConfig { fn default() -> Self { Self { activity_limit: 50, } } } /// Builder for UserService following established pattern #[derive(Default)] pub struct UserServiceBuilder { activity_limit: Option, } impl UserServiceBuilder { pub fn new() -> Self { Self::default() } pub fn activity_limit(mut self, limit: usize) -> Self { self.activity_limit = Some(limit); self } pub fn build(self) -> Result { let config = UserServiceConfig { activity_limit: self.activity_limit.unwrap_or(50), }; Ok(UserService { config }) } } /// Main UserService for managing user dashboard data #[derive(Clone)] pub struct UserService { config: UserServiceConfig, } impl UserService { /// Create a new builder for UserService pub fn builder() -> UserServiceBuilder { UserServiceBuilder::new() } /// Get user activities with optional limit pub fn get_user_activities(&self, user_email: &str, limit: Option) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { let mut activities = persistent_data.user_activities; // Sort by timestamp (newest first) activities.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); // Apply limit let limit = limit.unwrap_or(self.config.activity_limit); activities.truncate(limit); activities } else { Vec::new() } } /// Get user purchase history from transactions pub fn get_purchase_history(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { let purchases: Vec = persistent_data.transactions .into_iter() .filter(|t| matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. })) .collect(); purchases } else { Vec::new() } } /// Get or calculate usage statistics pub fn get_usage_statistics(&self, user_email: &str) -> Option { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { // Return existing statistics or calculate new ones if let Some(stats) = persistent_data.usage_statistics { Some(stats) } else { Some(self.calculate_usage_statistics(user_email, &persistent_data)) } } else { None } } /// Get active deployments for user pub fn get_active_deployments(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { // Convert active product rentals to deployments let deployments: Vec = persistent_data.active_product_rentals .into_iter() .filter(|r| r.status == "Active") .map(|rental| crate::models::user::UserDeployment { id: rental.id, app_name: rental.product_name, status: crate::models::user::DeploymentStatus::Active, cost_per_month: rental.monthly_cost, deployed_at: chrono::DateTime::parse_from_rfc3339(&rental.start_date) .map(|dt| dt.with_timezone(&Utc)) .unwrap_or_else(|_| Utc::now()), provider: rental.provider_email, region: rental.metadata.get("region") .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string(), resource_usage: rental.metadata.get("resource_usage") .and_then(|v| serde_json::from_value(v.clone()).ok()) .unwrap_or_default(), }) .collect(); deployments } else { Vec::new() } } /// Get user's published services (for service-provider dashboard) pub fn get_user_published_services(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { persistent_data.services } else { Vec::new() } } /// Get user's purchased services (for user dashboard) - derived from service bookings pub fn get_user_purchased_services(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { // Convert service bookings to service view for user dashboard // IMPORTANT: Only show bookings where THIS user is the customer let purchased_services: Vec = persistent_data.service_bookings .into_iter() .filter(|b| b.customer_email == user_email) .map(|booking| { let hourly_rate = (booking.budget / rust_decimal::Decimal::from(booking.estimated_hours.unwrap_or(1).max(1))).to_string().parse::().unwrap_or(0); crate::models::user::Service { id: booking.service_id, name: booking.service_name, category: "Service".to_string(), // Default category for purchased services description: booking.description.unwrap_or_else(|| "Service booking".to_string()), price_usd: booking.budget, hourly_rate_usd: Some(rust_decimal::Decimal::new(hourly_rate as i64, 0)), availability: true, created_at: chrono::DateTime::parse_from_rfc3339(&booking.requested_date) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|_| chrono::Utc::now()), updated_at: chrono::DateTime::parse_from_rfc3339(&booking.requested_date) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|_| chrono::Utc::now()), price_per_hour_usd: hourly_rate, status: booking.status, clients: 0, // Users don't see client count for purchased services rating: 4.5, // Default rating total_hours: booking.estimated_hours, } }) .collect(); purchased_services } else { Vec::new() } } /// Get user's purchased applications (for user dashboard) - derived from deployments pub fn get_user_applications(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { // Convert application deployments to application view for user dashboard // IMPORTANT: Only show deployments where THIS user is the customer let purchased_apps: Vec = persistent_data.application_deployments .into_iter() .filter(|d| d.status == "Active" && d.customer_email == user_email) .map(|deployment| crate::models::user::PublishedApp { id: deployment.app_id, name: deployment.app_name, description: Some("User-deployed application".to_string()), category: "Application".to_string(), // Default category for purchased apps version: "1.0.0".to_string(), // Default version price_usd: rust_decimal::Decimal::ZERO, deployment_count: 1, status: deployment.status, created_at: chrono::DateTime::parse_from_rfc3339(&deployment.created_at) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|_| chrono::Utc::now()), updated_at: chrono::DateTime::parse_from_rfc3339(&deployment.last_updated) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|_| chrono::Utc::now()), auto_scaling: Some(false), auto_healing: deployment.auto_healing, revenue_history: Vec::new(), deployments: 1, // User has 1 deployment of this app rating: 4.5, // Default rating monthly_revenue_usd: rust_decimal::Decimal::ZERO, // Users don't earn revenue from purchased apps last_updated: chrono::DateTime::parse_from_rfc3339(&deployment.last_updated) .map(|dt| dt.with_timezone(&chrono::Utc)) .unwrap_or_else(|_| chrono::Utc::now()), }) .collect(); purchased_apps } else { Vec::new() } } /// Get user's active compute resources from rentals pub fn get_user_compute_resources(&self, user_email: &str) -> Vec { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { let compute_resources: Vec = persistent_data.active_product_rentals .into_iter() .filter(|r| r.status == "Active") .map(|rental| crate::models::user::UserComputeResource { id: rental.product_name.clone(), resource_type: rental.metadata.get("type") .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string(), specs: rental.metadata.get("specs") .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string(), location: rental.metadata.get("location") .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string(), status: rental.status.clone(), sla: rental.metadata.get("sla") .and_then(|v| v.as_str()) .unwrap_or("Unknown") .to_string(), monthly_cost: rental.monthly_cost, provider: rental.provider_email, resource_usage: rental.metadata.get("resource_usage") .and_then(|v| serde_json::from_value(v.clone()).ok()) .unwrap_or_default(), }) .collect(); compute_resources } else { Vec::new() } } /// Calculate comprehensive user metrics pub fn calculate_user_metrics(&self, user_email: &str) -> crate::models::user::UserMetrics { if let Some(persistent_data) = UserPersistence::load_user_data(user_email) { // Calculate total spent this month let current_month = Utc::now().format("%Y-%m").to_string(); let total_spent_this_month: Decimal = persistent_data.transactions .iter() .filter(|t| { t.timestamp.format("%Y-%m").to_string() == current_month && matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. }) }) .map(|t| t.amount) .sum(); // Count active deployments let active_deployments_count = persistent_data.active_product_rentals .iter() .filter(|r| r.status == "Active") .count() as i32; // Calculate resource utilization from active rentals let resource_utilization = self.calculate_resource_utilization(&persistent_data.active_product_rentals); // Generate cost trend (last 6 months) let cost_trend = self.calculate_cost_trend(&persistent_data.transactions); crate::models::user::UserMetrics { total_spent_this_month, active_deployments_count, resource_utilization, cost_trend, wallet_balance: persistent_data.wallet_balance_usd, total_transactions: persistent_data.transactions.len() as i32, } } else { crate::models::user::UserMetrics::default() } } /// Add new user activity pub fn add_user_activity(&self, user_email: &str, activity: UserActivity) -> Result<(), String> { if let Some(mut persistent_data) = UserPersistence::load_user_data(user_email) { persistent_data.user_activities.push(activity); // Keep only recent activities (limit to prevent file bloat) persistent_data.user_activities.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); persistent_data.user_activities.truncate(100); UserPersistence::save_user_data(&persistent_data) .map_err(|e| format!("Failed to save user activity: {}", e)) } else { Err("User data not found".to_string()) } } /// Update user preferences pub fn update_user_preferences(&self, user_email: &str, preferences: UserPreferences) -> Result<(), String> { if let Some(mut persistent_data) = UserPersistence::load_user_data(user_email) { persistent_data.user_preferences = Some(preferences); UserPersistence::save_user_data(&persistent_data) .map_err(|e| format!("Failed to save user preferences: {}", e)) } else { Err("User data not found".to_string()) } } // Private helper methods fn calculate_usage_statistics(&self, _user_email: &str, persistent_data: &crate::services::user_persistence::UserPersistentData) -> UsageStatistics { let total_deployments = persistent_data.active_product_rentals.len() as i32; let total_spent = persistent_data.transactions .iter() .filter(|t| matches!(t.transaction_type, crate::models::user::TransactionType::Purchase { .. })) .map(|t| t.amount) .sum(); // Calculate favorite categories from purchase history let mut category_counts: HashMap = HashMap::new(); for rental in &persistent_data.active_product_rentals { if let Some(category) = rental.metadata.get("category").and_then(|v| v.as_str()) { *category_counts.entry(category.to_string()).or_insert(0) += 1; } } let mut favorite_categories_vec: Vec<(String, i32)> = category_counts .into_iter() .collect(); favorite_categories_vec.sort_by(|a, b| b.1.cmp(&a.1)); let favorite_categories = favorite_categories_vec .into_iter() .take(5) .map(|(category, _)| category) .collect(); UsageStatistics { cpu_usage: 15.0, // TODO: Calculate from actual deployments memory_usage: 45.0, // TODO: Calculate from actual deployments storage_usage: 60.0, // TODO: Calculate from actual deployments network_usage: 25.0, // TODO: Calculate from actual deployments total_deployments, active_services: persistent_data.services.len() as i32, total_spent, favorite_categories, usage_trends: Vec::new(), // TODO: Implement trend calculation login_frequency: 3.5, // TODO: Calculate from activity log preferred_regions: vec!["Amsterdam".to_string(), "New York".to_string()], // TODO: Calculate from deployments account_age_days: 90, // TODO: Calculate from creation date last_activity: chrono::Utc::now(), } } fn calculate_resource_utilization(&self, rentals: &[crate::services::user_persistence::ProductRental]) -> crate::models::user::ResourceUtilization { // Calculate average resource utilization across active rentals if rentals.is_empty() { return crate::models::user::ResourceUtilization { cpu: 0, memory: 0, storage: 0, network: 0, }; } let mut total_cpu = 0; let mut total_memory = 0; let mut total_storage = 0; let mut total_network = 0; let mut count = 0; for rental in rentals { if let Some(usage) = rental.metadata.get("resource_usage") { if let Ok(usage) = serde_json::from_value::(usage.clone()) { total_cpu += usage.cpu; total_memory += usage.memory; total_storage += usage.storage; total_network += usage.network; count += 1; } } } if count > 0 { crate::models::user::ResourceUtilization { cpu: total_cpu / count, memory: total_memory / count, storage: total_storage / count, network: total_network / count, } } else { crate::models::user::ResourceUtilization { cpu: 45, // Default reasonable values memory: 60, storage: 35, network: 25, } } } fn calculate_cost_trend(&self, transactions: &[Transaction]) -> Vec { // Calculate monthly spending for last 6 months let mut monthly_costs = vec![0; 6]; let current_date = Utc::now(); for transaction in transactions { if matches!(transaction.transaction_type, crate::models::user::TransactionType::Purchase { .. }) { let months_ago = (current_date.year() * 12 + current_date.month() as i32) - (transaction.timestamp.year() * 12 + transaction.timestamp.month() as i32); if months_ago >= 0 && months_ago < 6 { let index = (5 - months_ago) as usize; if index < monthly_costs.len() { monthly_costs[index] += transaction.amount.to_string().parse::().unwrap_or(0); } } } } monthly_costs } }