using FlowAPI.Application.DTOs; using FlowAPI.Application.Interfaces; using FlowAPI.Domain.Entities; using FlowAPI.Domain.Enums; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FlowAPI.Application.Services { public class AchievementService : IAchievementService { private readonly IAchievementRepository _achievementRepo; private readonly IUserAchievementRepository _userAchievementRepo; private readonly IUserRepository _userRepo; public AchievementService(IAchievementRepository achievementRepo, IUserAchievementRepository userAchievementRepo, IUserRepository userRepo) { _achievementRepo = achievementRepo; _userAchievementRepo = userAchievementRepo; _userRepo = userRepo; } public async Task> GetUserAchievementsAsync(Guid userId) { var allAchievements = await _achievementRepo.GetAllAsync(); var userAchievements = await _userAchievementRepo.GetByUserIdAsync(userId); var user = await _userRepo.GetByIdAsync(userId); var isAvatarUploaded = user != null && !string.IsNullOrEmpty(user.AvatarUrl); var result = new List(); foreach (var achievement in allAchievements) { var userRecord = userAchievements.FirstOrDefault(ua => ua.AchievementId == achievement.Id); // Retroactively unlock Self Portrait if user already has an avatar set but no record or locked record in DB if (achievement.Title == "Self Portrait" && isAvatarUploaded) { if (userRecord == null) { var newUa = new UserAchievement { UserId = userId, AchievementId = achievement.Id, CurrentValue = 1, IsUnlocked = true, UnlockedAt = DateTime.UtcNow }; userRecord = await _userAchievementRepo.CreateAsync(newUa); } else if (!userRecord.IsUnlocked) { userRecord.CurrentValue = 1; userRecord.IsUnlocked = true; userRecord.UnlockedAt = DateTime.UtcNow; userRecord = await _userAchievementRepo.UpdateAsync(userRecord); } } result.Add(new UserAchievementDto { Id = userRecord?.Id ?? Guid.Empty, AchievementId = achievement.Id, CurrentValue = userRecord?.CurrentValue ?? 0, IsUnlocked = userRecord?.IsUnlocked ?? false, UnlockedAt = userRecord?.UnlockedAt, Achievement = new AchievementDto { Id = achievement.Id, Title = achievement.Title, Description = achievement.Description, IconUrl = achievement.IconUrl, Category = achievement.Category, Type = achievement.Type, TargetValue = achievement.TargetValue, RewardPoints = achievement.RewardPoints } }); } return result; } public async Task ProcessEventAsync(Guid userId, string eventName, int amount = 1) { // 1. Find achievements triggered by this event var triggeredAchievements = await _achievementRepo.GetByTriggerEventAsync(eventName); if (!triggeredAchievements.Any()) return; // 2. Fetch user's current progress for these achievements var achievementIds = triggeredAchievements.Select(a => a.Id).ToList(); var userAchievements = await _userAchievementRepo.GetByUserAndAchievementIdsAsync(userId, achievementIds); foreach (var achievement in triggeredAchievements) { var ua = userAchievements.FirstOrDefault(u => u.AchievementId == achievement.Id); // If user doesn't have a tracking record yet, create it if (ua == null) { ua = new UserAchievement { UserId = userId, AchievementId = achievement.Id, CurrentValue = 0, IsUnlocked = false }; ua = await _userAchievementRepo.CreateAsync(ua); } // Skip if already unlocked if (ua.IsUnlocked) continue; // Update progress if (achievement.Type == AchievementType.Accumulative || achievement.Type == AchievementType.SingleAction) { ua.CurrentValue += amount; } // Check for unlock if (ua.CurrentValue >= achievement.TargetValue) { ua.CurrentValue = achievement.TargetValue; ua.IsUnlocked = true; ua.UnlockedAt = DateTime.UtcNow; } await _userAchievementRepo.UpdateAsync(ua); } } public async Task SeedAchievementsAsync() { var seedData = new List { new Achievement { Title = "Blank Canvas", Description = "Create your first project (Graph).", Category = "Projects", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 10, TriggerEventName = "GraphCreated" }, new Achievement { Title = "Connecting the Dots", Description = "Create 10 connections (Edges) between tasks.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 10, RewardPoints = 50, TriggerEventName = "EdgeCreated" }, new Achievement { Title = "Sketch Master", Description = "Create 100 task nodes overall.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 100, RewardPoints = 100, TriggerEventName = "NodeCreated" }, new Achievement { Title = "Daily Doodler", Description = "Track habits 7 times.", Category = "Habits", Type = AchievementType.Accumulative, TargetValue = 7, RewardPoints = 70, TriggerEventName = "HabitTracked" }, new Achievement { Title = "Decision Maker", Description = "Complete 50 tasks.", Category = "Tasks", Type = AchievementType.Accumulative, TargetValue = 50, RewardPoints = 50, TriggerEventName = "TaskCompleted" }, new Achievement { Title = "Clear Desk", Description = "Delete 5 projects.", Category = "System", Type = AchievementType.Accumulative, TargetValue = 5, RewardPoints = 20, TriggerEventName = "GraphDeleted" }, new Achievement { Title = "First Milestone", Description = "Complete your first task.", Category = "Tasks", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 15, TriggerEventName = "TaskCompleted" }, new Achievement { Title = "Self Portrait", Description = "Upload your profile avatar.", Category = "System", Type = AchievementType.SingleAction, TargetValue = 1, RewardPoints = 15, TriggerEventName = "AvatarUploaded" } }; var existing = await _achievementRepo.GetByTitlesAsync(seedData.Select(s => s.Title)); var existingTitles = existing.Select(e => e.Title).ToList(); var toAdd = seedData.Where(s => !existingTitles.Contains(s.Title)).ToList(); if (toAdd.Any()) { await _achievementRepo.AddRangeAsync(toAdd); } } } }