using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Pumps; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; namespace adas_core.Infrastructure.Repositories { public class PumpObservationRepository : MongoRepository, IPumpObservationRepository { private readonly ApiSettings _apiSettings; public PumpObservationRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { _apiSettings = apiSettings.Value; } public override string GetCollectionName() { return _apiSettings.PumpObservations ?? "pump_observations"; } public override async Task CreateIndexes() { var indexModels = new List> { // Timeline por bomba (consulta más frecuente) new CreateIndexModel( Builders.IndexKeys .Ascending(x => x.DeviceId) .Descending(x => x.Time), new CreateIndexOptions { Name = "ix_deviceId_time" }), // consultas por paciente new CreateIndexModel( Builders.IndexKeys.Ascending(x => x.PatientId), new CreateIndexOptions { Name = "ix_patientId" }), // TTL por antigüedad (si aplica retención directa en Mongo) // new CreateIndexModel( // Builders.IndexKeys.Ascending(x => x.Time), // new CreateIndexOptions { Name = "ttl_time", ExpireAfter = TimeSpan.FromDays(180) }) }; await Collection.Indexes.CreateManyAsync(indexModels); } public async Task InsertAsync(PumpObservation obs) { await Collection.InsertOneAsync(obs); } public async Task InsertManyAsync(IEnumerable? observations) { if (observations == null) return; var list = observations as IList ?? observations.ToList(); if (list.Count == 0) return; await Collection.InsertManyAsync(list); } public async Task> FindByDeviceIdAsync( string deviceId, DateTime? from = null, DateTime? to = null, int? limit = null) { var filter = Builders.Filter.Eq(x => x.DeviceId, deviceId); if (from.HasValue) filter &= Builders.Filter.Gte(x => x.Time, from.Value); if (to.HasValue) filter &= Builders.Filter.Lte(x => x.Time, to.Value); var query = Collection.Find(filter) .SortByDescending(x => x.Time); if (limit.HasValue) query = query.Limit(limit.Value) as IOrderedFindFluent; return await query.ToListAsync(); } // histórico por paciente public async Task> FindByPatientAsync( ObjectId patientId, DateTime? from = null, DateTime? to = null, int? limit = null) { var filter = Builders.Filter.Eq(x => x.PatientId, patientId); if (from.HasValue) filter &= Builders.Filter.Gte(x => x.Time, from.Value); if (to.HasValue) filter &= Builders.Filter.Lte(x => x.Time, to.Value); var query = Collection.Find(filter) .SortByDescending(x => x.Time); if (limit.HasValue) query = query.Limit(limit.Value) as IOrderedFindFluent; return await query.ToListAsync(); } public async Task> FindAllLastPatientObservationTimeAsync() { // Pipeline: // 1) Filtrar observaciones con PatientId no nulo // 2) Agrupar por PatientId // 3) Obtener el máximo Time // 4) Devolver diccionario var pipeline = new[] { new BsonDocument("$match", new BsonDocument { { "patientid", new BsonDocument("$ne", BsonNull.Value) } }), new BsonDocument("$group", new BsonDocument { { "_id", "$patientid" }, { "LastTime", new BsonDocument("$max", "$time") } }) }; var docs = await Collection.Aggregate(pipeline).ToListAsync(); var result = new Dictionary(); foreach (var doc in docs.Where(doc => doc["_id"].IsObjectId && doc["LastTime"].IsValidDateTime )) { result[doc["_id"].AsObjectId] = doc["LastTime"].ToUniversalTime(); } return result; } public async Task FindLastByDeviceIdAsync(string deviceId) { return await Collection .Find(x => x.DeviceId == deviceId) .SortByDescending(x => x.Time) .FirstOrDefaultAsync(); } public async Task> FindByPatientId(ObjectId? patientId) { return await Collection.Find(x => x.PatientId == patientId).ToListAsync(); } public async Task> AggregatedPatientLastObservations(ObjectId patientId, int num = 100) { var filterBuilder = Builders.Filter; var sortBuilder = Builders.Sort; var filter = filterBuilder.Eq(o => o.PatientId, patientId); var sort = sortBuilder.Descending("time"); var options = new FindOptions { Sort = sort, Limit = num }; var result = await Collection.FindAsync(filter, options); var observations = await result.ToListAsync(); var distinctObservations = observations.DistinctBy(m => new { m.Code, m.Name }).ToList(); var sortedObservations = distinctObservations.OrderByDescending(x => x.Time).ToList(); return sortedObservations; } public async Task DeleteByPatientId(ObjectId? patientId) { await Collection.DeleteManyAsync(x => x.PatientId == patientId); } public async Task DeleteOlderThanDaysAsync(int days, string? name = null) { var limitDate = DateTime.UtcNow.AddDays(-days); var filter = Builders.Filter.Lt(x => x.Time, limitDate); if(!string.IsNullOrEmpty(name)) filter &= Builders.Filter.Eq(x=> x.Name, name); return (await Collection.DeleteManyAsync(filter)).DeletedCount; } public async Task DeleteKeepLastNAsync(int maxCount) { // Para cada DeviceId: var deviceIds = await Collection .Distinct("DeviceId", FilterDefinition.Empty) .ToListAsync(); long totalDeleted = 0; foreach (var filter in deviceIds.Select(deviceId => Builders.Filter.Eq(x => x.DeviceId, deviceId) )) { var all = await Collection.Find(filter) .SortByDescending(x => x.Time) .ToListAsync(); if (all.Count <= maxCount) continue; var toDelete = all.Skip(maxCount).Select(x => x.Id).ToList(); var deleteFilter = Builders.Filter.In(x => x.Id, toDelete); var result = await Collection.DeleteManyAsync(deleteFilter); totalDeleted += result.DeletedCount; } return totalDeleted; } public async Task UpdateManyObjectIdByFieldAsync(string fieldName, ObjectId newId, ObjectId? oldId) { if (string.IsNullOrWhiteSpace(fieldName)) throw new ArgumentException("fieldName can't be null or empty.", nameof(fieldName)); // Normalize casing (Mongo is case-sensitive) // if (fieldName.Equals("patientid", StringComparison.OrdinalIgnoreCase)) // fieldName = nameof(PumpObservation.PatientId); var filter = Builders.Filter.Eq(fieldName, oldId); var update = Builders.Update.Set(fieldName, newId); var result = await Collection.UpdateManyAsync(filter, update); return result.ModifiedCount; } public async Task DeleteOlderNumberAsync(string name, int maxCount) { if (string.IsNullOrWhiteSpace(name)) return 0; // 1. Filtrar todas las observaciones con ese Name var filter = Builders.Filter.Eq(x => x.Name, name); // 2. Obtenerlas ordenadas por Time DESC (las más recientes primero) var all = await Collection .Find(filter) .SortByDescending(x => x.Time) .ToListAsync(); // 3. Si hay menos o igual al número permitido → no borrar nada if (all.Count <= maxCount) return 0; // 4. Seleccionar TODAS excepto las maxCount más recientes var toDeleteIds = all .Skip(maxCount) .Select(x => x.Id) .ToList(); if (toDeleteIds.Count == 0) return 0; // 5. Borrar las seleccionadas var deleteFilter = Builders.Filter.In(x => x.Id, toDeleteIds); var result = await Collection.DeleteManyAsync(deleteFilter); return result.DeletedCount; } public async Task> FindLastObservations(ObjectId patientId, string name) { if (string.IsNullOrWhiteSpace(name)) return []; var filter = Builders.Filter.And( Builders.Filter.Eq(x => x.PatientId, patientId), Builders.Filter.Eq(x => x.Name, name) ); return await Collection .Find(filter) .SortByDescending(x => x.Time) .Limit(2) .ToListAsync(); } } }