using System.Text.Json; using Microsoft.AspNetCore.SignalR; using Microsoft.EntityFrameworkCore; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Hubs; using LittleShop.Models; namespace LittleShop.Services; public class BotActivityService : IBotActivityService { private readonly LittleShopContext _context; private readonly IHubContext _hubContext; private readonly ILogger _logger; public BotActivityService( LittleShopContext context, IHubContext hubContext, ILogger logger) { _context = context; _hubContext = hubContext; _logger = logger; } public async Task LogActivityAsync(CreateBotActivityDto dto) { var activity = new BotActivity { Id = Guid.NewGuid(), BotId = dto.BotId, SessionIdentifier = dto.SessionIdentifier, UserDisplayName = dto.UserDisplayName, ActivityType = dto.ActivityType, ActivityDescription = dto.ActivityDescription, ProductId = dto.ProductId, ProductName = dto.ProductName, OrderId = dto.OrderId, CategoryName = dto.CategoryName, Value = dto.Value, Quantity = dto.Quantity, Platform = dto.Platform, DeviceType = dto.DeviceType, Location = dto.Location, Timestamp = DateTime.UtcNow, Metadata = dto.Metadata }; _context.BotActivities.Add(activity); await _context.SaveChangesAsync(); // Broadcast the activity to connected clients await BroadcastActivityAsync(activity); _logger.LogInformation("Activity logged: {User} - {Type} - {Description}", activity.UserDisplayName, activity.ActivityType, activity.ActivityDescription); return activity; } public async Task> GetRecentActivitiesAsync(int minutesBack = 5) { var cutoffTime = DateTime.UtcNow.AddMinutes(-minutesBack); var activities = await _context.BotActivities .Include(a => a.Bot) .Where(a => a.Timestamp >= cutoffTime) .OrderByDescending(a => a.Timestamp) .Take(100) .Select(a => MapToDto(a)) .ToListAsync(); return activities; } public async Task> GetActivitiesBySessionAsync(string sessionIdentifier) { var activities = await _context.BotActivities .Include(a => a.Bot) .Where(a => a.SessionIdentifier == sessionIdentifier) .OrderByDescending(a => a.Timestamp) .Take(200) .Select(a => MapToDto(a)) .ToListAsync(); return activities; } public async Task> GetActivitiesByBotAsync(Guid botId, int limit = 100) { var activities = await _context.BotActivities .Include(a => a.Bot) .Where(a => a.BotId == botId) .OrderByDescending(a => a.Timestamp) .Take(limit) .Select(a => MapToDto(a)) .ToListAsync(); return activities; } public async Task GetLiveActivitySummaryAsync() { var fiveMinutesAgo = DateTime.UtcNow.AddMinutes(-5); var oneMinuteAgo = DateTime.UtcNow.AddMinutes(-1); var recentActivities = await _context.BotActivities .Include(a => a.Bot) .Where(a => a.Timestamp >= fiveMinutesAgo) .ToListAsync(); var activeUsers = recentActivities .Where(a => a.Timestamp >= oneMinuteAgo) .Select(a => a.SessionIdentifier) .Distinct() .Count(); var activeUserNames = recentActivities .Where(a => a.Timestamp >= oneMinuteAgo) .Select(a => a.UserDisplayName) .Distinct() .Take(10) .ToList(); var productViews = recentActivities .Where(a => a.ActivityType == "ViewProduct") .Count(); var cartsActive = recentActivities .Where(a => a.ActivityType == "AddToCart" || a.ActivityType == "UpdateCart") .Select(a => a.SessionIdentifier) .Distinct() .Count(); var totalCartValue = recentActivities .Where(a => a.ActivityType == "AddToCart" && a.Value.HasValue) .Sum(a => a.Value ?? 0); var summary = new LiveActivitySummaryDto { ActiveUsers = activeUsers, TotalActivitiesLast5Min = recentActivities.Count, ProductViewsLast5Min = productViews, CartsActiveNow = cartsActive, TotalValueInCartsNow = totalCartValue, ActiveUserNames = activeUserNames, RecentActivities = recentActivities .OrderByDescending(a => a.Timestamp) .Take(20) .Select(a => MapToDto(a)) .ToList() }; return summary; } public async Task> GetProductActivitiesAsync(Guid productId, int limit = 50) { var activities = await _context.BotActivities .Include(a => a.Bot) .Where(a => a.ProductId == productId) .OrderByDescending(a => a.Timestamp) .Take(limit) .Select(a => MapToDto(a)) .ToListAsync(); return activities; } public async Task> GetActivityTypeStatsAsync(int hoursBack = 24) { var cutoffTime = DateTime.UtcNow.AddHours(-hoursBack); var stats = await _context.BotActivities .Where(a => a.Timestamp >= cutoffTime) .GroupBy(a => a.ActivityType) .Select(g => new { Type = g.Key, Count = g.Count() }) .ToDictionaryAsync(x => x.Type, x => x.Count); return stats; } public async Task BroadcastActivityAsync(BotActivity activity) { try { var dto = MapToDto(activity); await _hubContext.Clients.All.SendAsync("NewActivity", dto); // Also send summary update var summary = await GetLiveActivitySummaryAsync(); await _hubContext.Clients.All.SendAsync("SummaryUpdate", summary); } catch (Exception ex) { _logger.LogError(ex, "Error broadcasting activity"); } } private static BotActivityDto MapToDto(BotActivity activity) { return new BotActivityDto { Id = activity.Id, BotId = activity.BotId, BotName = activity.Bot?.Name ?? "Unknown Bot", SessionIdentifier = activity.SessionIdentifier, UserDisplayName = activity.UserDisplayName, ActivityType = activity.ActivityType, ActivityDescription = activity.ActivityDescription, ProductId = activity.ProductId, ProductName = activity.ProductName, OrderId = activity.OrderId, CategoryName = activity.CategoryName, Value = activity.Value, Quantity = activity.Quantity, Platform = activity.Platform, DeviceType = activity.DeviceType, Location = activity.Location, Timestamp = activity.Timestamp, Metadata = activity.Metadata }; } }