using System.Text.Json; using Managing.Application.Abstractions.Repositories; using Managing.Domain.Synth.Models; using Managing.Infrastructure.Databases.PostgreSql.Entities; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; namespace Managing.Infrastructure.Databases.PostgreSql; public class PostgreSqlSynthRepository : ISynthRepository { private readonly ManagingDbContext _context; private readonly ILogger _logger; public PostgreSqlSynthRepository(ManagingDbContext context, ILogger logger) { _context = context; _logger = logger; } public async Task GetLeaderboardAsync(string cacheKey) { try { var entity = await _context.SynthMinersLeaderboards.AsNoTracking().FirstOrDefaultAsync(x => x.CacheKey == cacheKey); if (entity == null) { _logger.LogDebug($"[PG Synth] No leaderboard cache found for key: {cacheKey}"); return null; } var miners = JsonSerializer.Deserialize>(entity.MinersData); return new SynthMinersLeaderboard { Id = entity.Id.ToString(), Asset = entity.Asset, TimeIncrement = entity.TimeIncrement, SignalDate = entity.SignalDate, IsBacktest = entity.IsBacktest, Miners = miners ?? new List(), CreatedAt = entity.CreatedAt }; } catch (Exception ex) { _logger.LogError(ex, $"Error retrieving leaderboard cache for key: {cacheKey}"); return null; } } public async Task SaveLeaderboardAsync(SynthMinersLeaderboard leaderboard) { try { leaderboard.CreatedAt = DateTime.UtcNow; var cacheKey = leaderboard.GetCacheKey(); var minersData = JsonSerializer.Serialize(leaderboard.Miners); var existing = await _context.SynthMinersLeaderboards.FirstOrDefaultAsync(x => x.CacheKey == cacheKey); if (existing != null) { existing.Asset = leaderboard.Asset; existing.TimeIncrement = leaderboard.TimeIncrement; existing.SignalDate = leaderboard.SignalDate; existing.IsBacktest = leaderboard.IsBacktest; existing.MinersData = minersData; existing.CreatedAt = leaderboard.CreatedAt; _context.SynthMinersLeaderboards.Update(existing); _logger.LogDebug($"[PG Synth] Updated leaderboard in DB for key: {cacheKey}"); } else { var entity = new SynthMinersLeaderboardEntity { Id = Guid.NewGuid(), Asset = leaderboard.Asset, TimeIncrement = leaderboard.TimeIncrement, SignalDate = leaderboard.SignalDate, IsBacktest = leaderboard.IsBacktest, MinersData = minersData, CacheKey = cacheKey, CreatedAt = leaderboard.CreatedAt }; await _context.SynthMinersLeaderboards.AddAsync(entity); _logger.LogDebug($"[PG Synth] Saved new leaderboard to DB for key: {cacheKey}"); } await _context.SaveChangesAsync(); } catch (Exception ex) { _logger.LogError(ex, $"Error saving leaderboard cache for key: {leaderboard.GetCacheKey()}"); } } public async Task> GetIndividualPredictionsAsync(string asset, int timeIncrement, int timeLength, List minerUids, bool isBacktest, DateTime? signalDate) { try { var results = new List(); foreach (var minerUid in minerUids) { var cacheKey = $"{asset}_{timeIncrement}_{timeLength}_{minerUid}"; if (isBacktest && signalDate.HasValue) { cacheKey += $"_backtest_{signalDate.Value:yyyy-MM-dd-HH}"; } var entity = await _context.SynthPredictions.AsNoTracking().FirstOrDefaultAsync(x => x.CacheKey == cacheKey); if (entity != null) { var prediction = JsonSerializer.Deserialize(entity.PredictionData); if (prediction != null) { results.Add(new SynthPrediction { Id = entity.Id.ToString(), Asset = entity.Asset, MinerUid = entity.MinerUid, TimeIncrement = entity.TimeIncrement, TimeLength = entity.TimeLength, SignalDate = entity.SignalDate, IsBacktest = entity.IsBacktest, Prediction = prediction, CreatedAt = entity.CreatedAt }); } } } if (results.Any()) { _logger.LogDebug($"[PG Synth] Retrieved {results.Count}/{minerUids.Count} individual predictions for {asset}"); } else { _logger.LogDebug($"[PG Synth] No individual predictions found for {asset}"); } return results; } catch (Exception ex) { _logger.LogError(ex, $"Error retrieving individual predictions cache for asset: {asset}"); return new List(); } } public async Task SaveIndividualPredictionAsync(SynthPrediction prediction) { try { prediction.CreatedAt = DateTime.UtcNow; var cacheKey = prediction.GetCacheKey(); var predictionData = JsonSerializer.Serialize(prediction.Prediction); var existing = await _context.SynthPredictions.FirstOrDefaultAsync(x => x.CacheKey == cacheKey).ConfigureAwait(false); if (existing != null) { existing.Asset = prediction.Asset; existing.MinerUid = prediction.MinerUid; existing.TimeIncrement = prediction.TimeIncrement; existing.TimeLength = prediction.TimeLength; existing.SignalDate = prediction.SignalDate; existing.IsBacktest = prediction.IsBacktest; existing.PredictionData = predictionData; existing.CreatedAt = prediction.CreatedAt; _context.SynthPredictions.Update(existing); _logger.LogDebug($"[PG Synth] Updated individual prediction for miner {prediction.MinerUid}"); } else { var entity = new SynthPredictionEntity { Id = Guid.NewGuid(), Asset = prediction.Asset, MinerUid = prediction.MinerUid, TimeIncrement = prediction.TimeIncrement, TimeLength = prediction.TimeLength, SignalDate = prediction.SignalDate, IsBacktest = prediction.IsBacktest, PredictionData = predictionData, CacheKey = cacheKey, CreatedAt = prediction.CreatedAt }; await _context.SynthPredictions.AddAsync(entity).ConfigureAwait(false); _logger.LogDebug($"[PG Synth] Saved new individual prediction for miner {prediction.MinerUid}"); } await _context.SaveChangesAsync().ConfigureAwait(false); } catch (Exception ex) { _logger.LogError(ex, $"Error saving individual prediction cache for miner {prediction.MinerUid}: {ex.Message}"); } } public async Task SaveIndividualPredictionsAsync(List predictions) { if (!predictions.Any()) return; try { var saveTasks = predictions.Select(SaveIndividualPredictionAsync); await Task.WhenAll(saveTasks); _logger.LogInformation($"[PG Synth] Successfully saved {predictions.Count} individual predictions to DB"); } catch (Exception ex) { _logger.LogError(ex, $"Error saving batch of {predictions.Count} individual predictions"); } } public async Task CleanupOldDataAsync(int retentionDays = 30) { try { var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays); var oldLeaderboards = _context.SynthMinersLeaderboards.Where(x => x.CreatedAt < cutoffDate); _context.SynthMinersLeaderboards.RemoveRange(oldLeaderboards); var oldPredictions = _context.SynthPredictions.Where(x => x.CreatedAt < cutoffDate); _context.SynthPredictions.RemoveRange(oldPredictions); await _context.SaveChangesAsync(); _logger.LogInformation($"[PG Synth] Cleaned up old Synth cache data older than {retentionDays} days"); } catch (Exception ex) { _logger.LogError(ex, $"Error during cleanup of old Synth cache data"); } } }