using Marathon.Application.Abstractions; using Marathon.Domain.Entities; using Marathon.Domain.ValueObjects; using Microsoft.EntityFrameworkCore; namespace Marathon.Infrastructure.Persistence.Repositories; internal sealed class SnapshotRepository : ISnapshotRepository { private readonly MarathonDbContext _db; public SnapshotRepository(MarathonDbContext db) => _db = db; public async Task> ListAsync(CancellationToken ct = default) { var entities = await _db.Snapshots.AsNoTracking() .Include(s => s.Bets) .ToListAsync(ct); return entities.Select(Mapping.ToDomain).ToList().AsReadOnly(); } public async Task> ListByEventAsync( EventId eventId, DateTimeOffset from, DateTimeOffset to, CancellationToken ct = default) { var fromStr = from.ToString("O"); var toStr = to.ToString("O"); var entities = await _db.Snapshots.AsNoTracking() .Include(s => s.Bets) .Where(s => s.EventCode == eventId.Value && s.CapturedAt.CompareTo(fromStr) >= 0 && s.CapturedAt.CompareTo(toStr) <= 0) .ToListAsync(ct); return entities.Select(Mapping.ToDomain).ToList().AsReadOnly(); } public async Task>> ListByEventsAsync( IReadOnlyCollection eventIds, DateTimeOffset from, DateTimeOffset to, CancellationToken ct = default) { ArgumentNullException.ThrowIfNull(eventIds); var result = new Dictionary>(eventIds.Count); if (eventIds.Count == 0) return result; var ids = eventIds.Select(e => e.Value).Distinct().ToArray(); var fromStr = from.ToString("O"); var toStr = to.ToString("O"); var entities = await _db.Snapshots.AsNoTracking() .Include(s => s.Bets) .Where(s => ids.Contains(s.EventCode) && s.CapturedAt.CompareTo(fromStr) >= 0 && s.CapturedAt.CompareTo(toStr) <= 0) .ToListAsync(ct); var grouped = entities .GroupBy(e => e.EventCode) .ToDictionary(g => g.Key, g => g.Select(Mapping.ToDomain).ToList()); foreach (var id in eventIds) { result[id] = grouped.TryGetValue(id.Value, out var list) ? list.AsReadOnly() : Array.Empty(); } return result; } public async Task AddAsync(OddsSnapshot entity, CancellationToken ct = default) { var efEntity = Mapping.ToEntity(entity); await _db.Snapshots.AddAsync(efEntity, ct); } public async Task SaveChangesAsync(CancellationToken ct = default) => await _db.SaveChangesAsync(ct); public async Task GetLatestPreMatchAsync( EventId eventId, DateTimeOffset atOrBefore, CancellationToken ct = default) { // OddsSource enum: PreMatch == 0. Inlined as an int constant to keep the // expression EF-translatable (the IL would otherwise carry a cast). const int preMatchSource = (int)Marathon.Domain.Enums.OddsSource.PreMatch; var toStr = atOrBefore.ToString("O"); var entity = await _db.Snapshots.AsNoTracking() .Include(s => s.Bets) .Where(s => s.EventCode == eventId.Value && s.Source == preMatchSource && s.CapturedAt.CompareTo(toStr) <= 0) .OrderByDescending(s => s.CapturedAt) .FirstOrDefaultAsync(ct); return entity is null ? null : Mapping.ToDomain(entity); } }