using Managing.Application.Abstractions.Repositories; using Managing.Domain.Bots; using Microsoft.EntityFrameworkCore; using static Managing.Common.Enums; namespace Managing.Infrastructure.Databases.PostgreSql; public class PostgreSqlBotRepository : IBotRepository { private readonly ManagingDbContext _context; public PostgreSqlBotRepository(ManagingDbContext context) { _context = context; } public async Task InsertBotAsync(Bot bot) { bot.CreateDate = DateTime.UtcNow; var entity = PostgreSqlMappers.Map(bot); // Set the UserId if user is provided if (bot.User != null) { var userEntity = await _context.Users .AsNoTracking() .FirstOrDefaultAsync(u => u.Id == bot.User.Id) .ConfigureAwait(false); if (userEntity == null) { throw new InvalidOperationException($"User with id '{bot.User.Id}' not found"); } entity.UserId = userEntity.Id; } await _context.Bots.AddAsync(entity).ConfigureAwait(false); await _context.SaveChangesAsync().ConfigureAwait(false); } public async Task> GetBotsAsync() { var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } public async Task UpdateBot(Bot bot) { var existingEntity = await _context.Bots .AsTracking() .FirstOrDefaultAsync(b => b.Identifier == bot.Identifier) .ConfigureAwait(false); if (existingEntity == null) { throw new InvalidOperationException($"Bot backup with identifier '{bot.Identifier}' not found"); } // Update the existing entity properties directly instead of creating a new one existingEntity.Name = bot.Name; existingEntity.Ticker = bot.Ticker; existingEntity.Status = bot.Status; existingEntity.StartupTime = bot.StartupTime; existingEntity.TradeWins = bot.TradeWins; existingEntity.TradeLosses = bot.TradeLosses; existingEntity.Pnl = bot.Pnl; existingEntity.Roi = bot.Roi; existingEntity.Volume = bot.Volume; existingEntity.Fees = bot.Fees; existingEntity.UpdatedAt = DateTime.UtcNow; existingEntity.LongPositionCount = bot.LongPositionCount; existingEntity.ShortPositionCount = bot.ShortPositionCount; await _context.SaveChangesAsync().ConfigureAwait(false); } public async Task DeleteBot(Guid identifier) { var entity = await _context.Bots .AsTracking() .FirstOrDefaultAsync(b => b.Identifier == identifier) .ConfigureAwait(false); if (entity == null) { throw new InvalidOperationException($"Bot backup with identifier '{identifier}' not found"); } _context.Bots.Remove(entity); await _context.SaveChangesAsync().ConfigureAwait(false); } public async Task GetBotByIdentifierAsync(Guid identifier) { var entity = await _context.Bots .AsNoTracking() .Include(m => m.User) .FirstOrDefaultAsync(b => b.Identifier == identifier) .ConfigureAwait(false); return PostgreSqlMappers.Map(entity); } public async Task> GetBotsByUserIdAsync(int id) { var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .Where(b => b.UserId == id) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } public async Task> GetBotsByStatusAsync(BotStatus status) { var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .Where(b => b.Status == status) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } public async Task GetBotByNameAsync(string name) { var entity = await _context.Bots .AsNoTracking() .Include(m => m.User) .FirstOrDefaultAsync(b => b.Name == name) .ConfigureAwait(false); return PostgreSqlMappers.Map(entity); } public async Task> GetBotsByIdsAsync(IEnumerable identifiers) { try { await PostgreSqlConnectionHelper.EnsureConnectionOpenAsync(_context); var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .Where(b => identifiers.Contains(b.Identifier)) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } finally { // Always ensure the connection is closed after the operation await PostgreSqlConnectionHelper.SafeCloseConnectionAsync(_context); } } public async Task<(IEnumerable Bots, int TotalCount)> GetBotsPaginatedAsync( int pageNumber, int pageSize, BotStatus? status = null, string? name = null, string? ticker = null, string? agentName = null, string sortBy = "CreateDate", string sortDirection = "Desc") { // Build the query with filters var query = _context.Bots .AsNoTracking() .Include(m => m.User) .AsQueryable(); // Apply filters if (status.HasValue) { query = query.Where(b => b.Status == status.Value); } if (!string.IsNullOrWhiteSpace(name)) { query = query.Where(b => EF.Functions.ILike(b.Name, $"%{name}%")); } if (!string.IsNullOrWhiteSpace(ticker)) { query = query.Where(b => EF.Functions.ILike(b.Ticker.ToString(), $"%{ticker}%")); } if (!string.IsNullOrWhiteSpace(agentName)) { query = query.Where(b => b.User != null && EF.Functions.ILike(b.User.AgentName, $"%{agentName}%")); } // Get total count before applying pagination var totalCount = await query.CountAsync().ConfigureAwait(false); // Apply sorting query = sortBy.ToLower() switch { "name" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Name) : query.OrderByDescending(b => b.Name), "ticker" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Ticker) : query.OrderByDescending(b => b.Ticker), "status" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Status) : query.OrderByDescending(b => b.Status), "startuptime" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.StartupTime) : query.OrderByDescending(b => b.StartupTime), "pnl" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.Pnl) : query.OrderByDescending(b => b.Pnl), "winrate" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.TradeWins / (b.TradeWins + b.TradeLosses)) : query.OrderByDescending(b => b.TradeWins / (b.TradeWins + b.TradeLosses)), "agentname" => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.User.AgentName) : query.OrderByDescending(b => b.User.AgentName), _ => sortDirection.ToLower() == "asc" ? query.OrderBy(b => b.CreateDate) : query.OrderByDescending(b => b.CreateDate) // Default to CreateDate }; // Apply pagination var skip = (pageNumber - 1) * pageSize; var entities = await query .Skip(skip) .Take(pageSize) .ToListAsync() .ConfigureAwait(false); var bots = PostgreSqlMappers.Map(entities); return (bots, totalCount); } public async Task> GetTopBotsByPnLAsync(IEnumerable statuses, int count = 3) { var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .Where(b => statuses.Contains(b.Status)) .OrderByDescending(b => b.Pnl) .Take(count) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } public async Task> GetTopBotsByRoiAsync(IEnumerable statuses, int count = 3) { var entities = await _context.Bots .AsNoTracking() .Include(m => m.User) .Where(b => statuses.Contains(b.Status)) .OrderByDescending(b => b.Roi) .Take(count) .ToListAsync() .ConfigureAwait(false); return PostgreSqlMappers.Map(entities); } }