using System; using System.Collections.Generic; using System.Linq; using System.Security.Cryptography; using System.Text.Json; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using LittleShop.Data; using LittleShop.DTOs; using LittleShop.Enums; using LittleShop.Models; namespace LittleShop.Services; public class BotService : IBotService { private readonly LittleShopContext _context; private readonly ILogger _logger; public BotService(LittleShopContext context, ILogger logger) { _context = context; _logger = logger; } public async Task RegisterBotAsync(BotRegistrationDto dto) { _logger.LogInformation("Registering bot: {BotName} (Type: {BotType})", dto.Name, dto.Type); // Check if a bot with the same name and type already exists var existingBot = await _context.Bots .FirstOrDefaultAsync(b => b.Name == dto.Name && b.Type == dto.Type); if (existingBot != null) { _logger.LogInformation("Bot already exists: {BotId}. Updating existing bot instead of creating duplicate.", existingBot.Id); // Update existing bot existingBot.Description = dto.Description; existingBot.Version = dto.Version; existingBot.Settings = JsonSerializer.Serialize(dto.InitialSettings); existingBot.PersonalityName = string.IsNullOrEmpty(dto.PersonalityName) ? existingBot.PersonalityName : dto.PersonalityName; existingBot.Status = BotStatus.Active; existingBot.IsActive = true; existingBot.LastConfigSyncAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Existing bot updated: {BotId}", existingBot.Id); return new BotRegistrationResponseDto { BotId = existingBot.Id, BotKey = existingBot.BotKey, Name = existingBot.Name, Settings = dto.InitialSettings }; } // Create new bot if none exists var botKey = await GenerateBotKeyAsync(); var bot = new Bot { Id = Guid.NewGuid(), Name = dto.Name, Description = dto.Description, Type = dto.Type, BotKey = botKey, Status = BotStatus.Active, Settings = JsonSerializer.Serialize(dto.InitialSettings), Version = dto.Version, PersonalityName = string.IsNullOrEmpty(dto.PersonalityName) ? AssignDefaultPersonality(dto.Name) : dto.PersonalityName, CreatedAt = DateTime.UtcNow, IsActive = true }; _context.Bots.Add(bot); await _context.SaveChangesAsync(); _logger.LogInformation("New bot registered successfully: {BotId}", bot.Id); return new BotRegistrationResponseDto { BotId = bot.Id, BotKey = botKey, Name = bot.Name, Settings = dto.InitialSettings }; } public async Task AuthenticateBotAsync(string botKey) { var bot = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .FirstOrDefaultAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active); if (bot == null) { _logger.LogWarning("Authentication failed for bot key: {BotKey}", botKey.Substring(0, 8) + "..."); return null; } // Update last seen bot.LastSeenAt = DateTime.UtcNow; await _context.SaveChangesAsync(); return MapToDto(bot); } public async Task GetBotByIdAsync(Guid id) { var bot = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .FirstOrDefaultAsync(b => b.Id == id); return bot != null ? MapToDto(bot) : null; } public async Task GetBotByKeyAsync(string botKey) { var bot = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .FirstOrDefaultAsync(b => b.BotKey == botKey); return bot != null ? MapToDto(bot) : null; } public async Task GetBotByPlatformUsernameAsync(int platformType, string platformUsername) { try { var bot = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .FirstOrDefaultAsync(b => b.Type == (BotType)platformType && b.PlatformUsername == platformUsername && b.Status != BotStatus.Deleted); return bot != null ? MapToDto(bot) : null; } catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table")) { // Tables don't exist yet - return null return null; } } public async Task> GetAllBotsAsync() { try { var bots = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .OrderByDescending(b => b.CreatedAt) .ToListAsync(); return bots.Select(MapToDto); } catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table")) { // Tables don't exist yet - return empty list return new List(); } } public async Task> GetActiveBots() { try { var bots = await _context.Bots .Include(b => b.Sessions) .Include(b => b.Metrics) .Where(b => b.Status == BotStatus.Active && b.IsActive) .OrderByDescending(b => b.LastSeenAt) .ToListAsync(); return bots.Select(MapToDto); } catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table")) { // Tables don't exist yet - return empty list return new List(); } } public async Task UpdateBotSettingsAsync(Guid botId, UpdateBotSettingsDto dto) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return false; bot.Settings = JsonSerializer.Serialize(dto.Settings); bot.LastConfigSyncAt = DateTime.UtcNow; await _context.SaveChangesAsync(); _logger.LogInformation("Updated settings for bot {BotId}", botId); return true; } public async Task UpdateBotStatusAsync(Guid botId, BotStatus status) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return false; var oldStatus = bot.Status; bot.Status = status; bot.IsActive = status == BotStatus.Active; await _context.SaveChangesAsync(); _logger.LogInformation("Bot {BotId} status changed from {OldStatus} to {NewStatus}", botId, oldStatus, status); return true; } public async Task RecordHeartbeatAsync(Guid botId, BotHeartbeatDto dto) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return false; bot.LastSeenAt = DateTime.UtcNow; bot.Version = dto.Version; bot.IpAddress = dto.IpAddress; // Record uptime metric var uptimeMetric = new BotMetric { Id = Guid.NewGuid(), BotId = botId, MetricType = MetricType.Uptime, Value = 1, Metadata = JsonSerializer.Serialize(dto.Status), RecordedAt = DateTime.UtcNow, Category = "System", Description = "Heartbeat" }; _context.BotMetrics.Add(uptimeMetric); await _context.SaveChangesAsync(); return true; } public async Task DeleteBotAsync(Guid botId) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return false; bot.Status = BotStatus.Deleted; bot.IsActive = false; await _context.SaveChangesAsync(); _logger.LogInformation("Bot {BotId} marked as deleted", botId); return true; } public async Task> GetBotSettingsAsync(Guid botId) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return new Dictionary(); try { return JsonSerializer.Deserialize>(bot.Settings) ?? new Dictionary(); } catch { return new Dictionary(); } } public async Task ValidateBotKeyAsync(string botKey) { return await _context.Bots.AnyAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active); } public Task GenerateBotKeyAsync() { const string prefix = "bot_"; const int keyLength = 32; using var rng = RandomNumberGenerator.Create(); var bytes = new byte[keyLength]; rng.GetBytes(bytes); var key = prefix + Convert.ToBase64String(bytes) .Replace("+", "") .Replace("/", "") .Replace("=", "") .Substring(0, keyLength); return Task.FromResult(key); } public async Task UpdatePlatformInfoAsync(Guid botId, UpdatePlatformInfoDto dto) { var bot = await _context.Bots.FindAsync(botId); if (bot == null) return false; bot.PlatformUsername = dto.PlatformUsername; bot.PlatformDisplayName = dto.PlatformDisplayName; bot.PlatformId = dto.PlatformId; await _context.SaveChangesAsync(); _logger.LogInformation("Updated platform info for bot {BotId}: @{Username} ({DisplayName})", botId, dto.PlatformUsername, dto.PlatformDisplayName); return true; } private BotDto MapToDto(Bot bot) { var settings = new Dictionary(); try { settings = JsonSerializer.Deserialize>(bot.Settings) ?? new Dictionary(); } catch { } var activeSessions = bot.Sessions.Count(s => !s.EndedAt.HasValue); var totalRevenue = bot.Sessions.Sum(s => s.TotalSpent); var totalOrders = bot.Sessions.Sum(s => s.OrderCount); return new BotDto { Id = bot.Id, Name = bot.Name, Description = bot.Description, Type = bot.Type, Status = bot.Status, CreatedAt = bot.CreatedAt, LastSeenAt = bot.LastSeenAt, LastConfigSyncAt = bot.LastConfigSyncAt, IsActive = bot.IsActive, Version = bot.Version, IpAddress = bot.IpAddress, PlatformUsername = bot.PlatformUsername, PlatformDisplayName = bot.PlatformDisplayName, PlatformId = bot.PlatformId, PersonalityName = bot.PersonalityName, Settings = settings, TotalSessions = bot.Sessions.Count, ActiveSessions = activeSessions, TotalRevenue = totalRevenue, TotalOrders = totalOrders }; } private string AssignDefaultPersonality(string botName) { var personalities = new[] { "Alan", "Dave", "Sarah", "Mike", "Emma", "Tom" }; // Smart assignment based on name var lowerName = botName.ToLower(); if (lowerName.Contains("alan")) return "Alan"; if (lowerName.Contains("dave")) return "Dave"; if (lowerName.Contains("sarah")) return "Sarah"; if (lowerName.Contains("mike")) return "Mike"; if (lowerName.Contains("emma")) return "Emma"; if (lowerName.Contains("tom")) return "Tom"; // Random assignment if no match var random = new Random(); return personalities[random.Next(personalities.Length)]; } }