littleshop/LittleShop/Services/BotService.cs
2025-08-27 18:02:39 +01:00

330 lines
10 KiB
C#

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<BotService> _logger;
public BotService(LittleShopContext context, ILogger<BotService> logger)
{
_context = context;
_logger = logger;
}
public async Task<BotRegistrationResponseDto> RegisterBotAsync(BotRegistrationDto dto)
{
_logger.LogInformation("Registering new bot: {BotName}", dto.Name);
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("Bot registered successfully: {BotId}", bot.Id);
return new BotRegistrationResponseDto
{
BotId = bot.Id,
BotKey = botKey,
Name = bot.Name,
Settings = dto.InitialSettings
};
}
public async Task<BotDto?> 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<BotDto?> 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<BotDto?> 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<IEnumerable<BotDto>> 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<BotDto>();
}
}
public async Task<IEnumerable<BotDto>> 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<BotDto>();
}
}
public async Task<bool> 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<bool> 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<bool> 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<bool> 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<Dictionary<string, object>> GetBotSettingsAsync(Guid botId)
{
var bot = await _context.Bots.FindAsync(botId);
if (bot == null)
return new Dictionary<string, object>();
try
{
return JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings)
?? new Dictionary<string, object>();
}
catch
{
return new Dictionary<string, object>();
}
}
public async Task<bool> ValidateBotKeyAsync(string botKey)
{
return await _context.Bots.AnyAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active);
}
public Task<string> 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<bool> 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<string, object>();
try
{
settings = JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings)
?? new Dictionary<string, object>();
}
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)];
}
}