using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.DTO; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.MongoModels; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using Newtonsoft.Json; using Serilog; using System.Text.RegularExpressions; namespace adas_core.Infrastructure.Repositories; /// /// Represents a MongoDB-backed repository for entities, inheriting persistence functionality from and implementing the contract. /// public class UnitRepository : MongoRepository, IUnitRepository { #region Properties private readonly ApiSettings _apiSettings; #endregion #region Constructor public UnitRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { _apiSettings = apiSettings.Value; } #endregion #region Methods #region Create /// /// Inserts a new into the underlying collection and returns the persisted instance retrieved by its identifier. /// If the insertion fails, the error is logged and the method returns null as a fallback. /// /// The to be inserted into the collection. /// The inserted as returned by the lookup by identifier, or null if an exception occurred during insertion. public async Task InsertOneUnit(Unit unit) { try { await Collection.InsertOneAsync(unit); return await FindById(unit.Id); } catch (Exception ex) { Log.Error("Error inserting Unit: {unit}. Exception: {ex}", JsonConvert.SerializeObject(unit, Formatting.Indented), ex); return null; } } #endregion #region Read /// /// Gets the collection name for units, returning the configured value from API settings or falling back to the default "units" when not specified. /// /// The collection name for units, or "units" if no custom value is configured in the API settings. public override string GetCollectionName() { return _apiSettings.Units ?? "units"; } // public async Task FindByLocation(PatientLocation location) // { // var filter = Builders.Filter.ElemMatch(x => x.PointOfCares, poc => poc.Bed == location.Bed && poc.UnitName == location.UnitName); // var result = await Collection.Find(filter).FirstOrDefaultAsync(); // // return result; // } /// /// Asynchronously finds a by its identifier in the underlying collection. /// Returns when no matching document is found. /// /// The identifier of the to locate. /// A instance if a match is found; otherwise, . public async Task FindById(object id) { var result = await Collection.FindAsync(Builders.Filter.Eq(x => x.Id, id)); return await result.FirstOrDefaultAsync(); } /// /// Asynchronously retrieves a by its unique identifier, returning null when no matching unit is found. /// /// The unique identifier of the unit to look up. /// A task that represents the asynchronous operation. The task result contains the matching , or null if no unit is found with the specified identifier. /// The method is not yet implemented. public Task FindById(string id) { throw new NotImplementedException(); } public async Task> FindByMasterListId(ObjectId id, MasterListType masterListType) { try { var propertyName = $"{masterListType}Id"; // nombre de la propiedad dinĂ¡micamente var filter = Builders.Filter.Eq(propertyName, id); var result = await Collection.Find(filter).ToListAsync(); return result; } catch (Exception ex) { Log.Error("Error searching unit by {masterlisttype} Id {id}. Exception: {ex}", masterListType.ToString(), id, ex); throw; } } /// /// Retrieves all documents that reference the specified master list identifier in any of their associated list properties (e.g., doctor, allergy, destination, diagnosis, procedure, test, service, treatment, language barrier, and similar lists). Uses an OR-based filter to match the identifier against every list id field, returning matching units; if an error occurs during the query, an empty collection is returned after logging the exception. /// /// The of the master list to search for across the unit's list reference fields. /// A task that yields an containing the matching units, or an empty list if no matches are found or an error occurs. public async Task> FindByMasterListId(ObjectId id) { try { var filterBuilder = Builders.Filter; var filters = new List> { filterBuilder.Or( filterBuilder.Eq(p => p.DoctorListId, id), filterBuilder.Eq(p => p.AllergyListId, id), filterBuilder.Eq(p => p.DestinationListId, id), filterBuilder.Eq(p => p.DiagnosisListId, id), filterBuilder.Eq(p => p.InsulationListId, id), filterBuilder.Eq(p => p.OriginListId, id), filterBuilder.Eq(p => p.ProcedureListId, id), filterBuilder.Eq(p => p.TestListId, id), filterBuilder.Eq(p => p.ServiceListId, id), filterBuilder.Eq(p => p.TreatmentListId, id), filterBuilder.Eq(p => p.LanguageBarrierListId, id), filterBuilder.Eq(p => p.AltableOptionListId, id), filterBuilder.Eq(p => p.DischargeStatusListId, id), filterBuilder.Eq(p => p.DoctorTypeListId, id), filterBuilder.Eq(p => p.InternalDestinationListId, id), filterBuilder.Eq(p => p.PassiveSittingListId, id), filterBuilder.Eq(p => p.GenericListId, id), filterBuilder.Eq(p => p.VisitOptionListId, id), filterBuilder.Eq(p => p.AccessControlListId, id), filterBuilder.Eq(p => p.TherapeuticCeilingListId, id), filterBuilder.Eq(p => p.MobilityOptionListId, id) ) }; var result = await Collection.Find(Builders.Filter.And(filters)).ToListAsync(); return result; } catch (Exception ex) { Log.Error("Error searching unit by masterlist Id {id}. Exception: {ex}", id.ToString(), ex); return new List(); } } /// /// Asynchronously counts the number of documents associated with the specified master list. /// The filter property is dynamically constructed based on the (e.g., "{Type}Id"), so the matching field depends on the provided master list type. /// /// The of the master list used to match units. /// The type of master list, which determines the property name used in the filter. /// The total number of documents that match the filter. public async Task CountUnitsByMasterListId(ObjectId id, MasterListType masterListType) { var propertyName = $"{masterListType}Id"; var filter = Builders.Filter.Eq(propertyName, id); var result = await Collection.CountDocumentsAsync(filter); return result; } /// /// Asynchronously finds and returns a matching the specified name from the collection, or null if no matching document is found. /// /// The name of the unit to look up using an exact equality match. /// A containing the matching if found; otherwise, null. public async Task FindByName(string unitName) { var result = await Collection.FindAsync(Builders.Filter.Eq(x => x.Name, unitName)); return await result.FirstOrDefaultAsync(); } // public async Task> FindByPointOfCare(PointOfCare pointOfCare) // { // var filter = Builders.Filter.ElemMatch(x => x.PointOfCares, poc => poc.Bed == pointOfCare.Bed && poc.Room == pointOfCare.Room && pointOfCare.unitName == poc.unitName); // var result = await Collection.Find(filter).ToListAsync(); // // return result; // } // public async Task FindByPointOfCare(PointOfCare pointOfCare) // { // var filter = Builders.Filter.ElemMatch(x => x.PointOfCares, poc => poc.Bed == pointOfCare.Bed && poc.Room == pointOfCare.Room && pointOfCare.UnitName == poc.UnitName); // var result = await Collection.Find(filter).FirstOrDefaultAsync(); // // return result; // } // public async Task> FindByUnitName(string unitName) // { // var filter = Builders.Filter.ElemMatch(x => x.PointOfCares, poc => poc.UnitName == unitName); // var result = await Collection.Find(filter).ToListAsync(); // // return result; // } /// /// Asynchronously retrieves all records from the underlying collection. /// /// A task that represents the asynchronous operation, containing a list of all documents found in the collection. public async Task> GetAll() { var result = await Collection.FindAsync(Builders.Filter.Empty); return result.ToList(); } /// /// Retrieves a paginated, sortable query of documents, optionally filtered by a case-insensitive text search applied to the Name and Title fields. /// /// The pagination filter containing the optional text search criteria used to narrow the result set. /// An representing the sorted and filtered query of units; if no filter request is supplied, an unfiltered query sorted by title is returned. public IFindFluent GetPaginatedUnits(PaginationFilter filter) { var filterBuilder = Builders.Filter; var sort = Builders.Sort.Ascending("title"); var filters = new List>(); if (filter.FilteredRequest == null) return CreateFindFluent(filters, sort); if (!string.IsNullOrEmpty(filter.FilteredRequest?.Text)) { var textFilter = filter.FilteredRequest.Text; var textFilterEscaped = Regex.Escape(textFilter); filters.Add( filterBuilder.Or( filterBuilder.Regex(p => p.Name, new BsonRegularExpression(textFilterEscaped, "i")), // Case-insensitive regex match for name filterBuilder.Regex(p => p.Title, new BsonRegularExpression(textFilterEscaped, "i")) // Case-insensitive regex match for title ) ); } return CreateFindFluent(filters, sort); } /// /// Creates a fluent find query for the collection, applying the supplied filters and sort definition. When the filter list is empty, an empty filter is used so that all documents are matched. /// /// The list of filter definitions to combine with a logical AND; if empty, no filtering is applied. /// The sort definition to apply to the query results. /// A fluent find interface for that can be further chained to project, limit, or execute the query. private IFindFluent CreateFindFluent(List> filters, SortDefinition sort) { var combinedFilter = filters.Any() ? Builders.Filter.And(filters) : Builders.Filter.Empty; return Collection.Find(combinedFilter).Sort(sort); } #endregion #region Update /// /// Updates an existing unit document in the collection, returning the updated document. /// Uses a filter on the unit's identifier and sets the updatable fields; returns null when no matching document is found. /// /// The unit containing the identifier and the new field values to be persisted. /// The updated unit after the operation, or null if no document matched the filter. public async Task UpdateUnit(Unit unit) { var filter = Builders.Filter.Eq("_id", unit.Id); var update = Builders.Update //.Set(c => c.Id, unit.Id) .Set(c => c.Title, unit.Title) .Set(c => c.Name, unit.Name) .Set(c => c.Configuration, unit.Configuration) .Set(c => c.AllergyListId, unit.AllergyListId) .Set(c => c.DestinationListId, unit.DestinationListId) .Set(c => c.InternalDestinationListId, unit.InternalDestinationListId) .Set(c => c.DiagnosisListId, unit.DiagnosisListId) .Set(c => c.DoctorListId, unit.DoctorListId) .Set(c => c.DoctorTypeListId, unit.DoctorTypeListId) .Set(c => c.InsulationListId, unit.InsulationListId) .Set(c => c.MobilityOptionListId, unit.MobilityOptionListId) .Set(c => c.OriginListId, unit.OriginListId) .Set(c => c.PatientStatusListId, unit.PatientStatusListId) .Set(c => c.ProcedureListId, unit.ProcedureListId) .Set(c => c.TestListId, unit.TestListId) .Set(c => c.ServiceListId, unit.ServiceListId) .Set(c => c.TherapeuticCeilingListId, unit.TherapeuticCeilingListId) .Set(c => c.TreatmentListId, unit.TreatmentListId) .Set(c => c.VisitOptionListId, unit.VisitOptionListId) .Set(c => c.AccessControlListId, unit.AccessControlListId) .Set(c => c.DischargeStatusListId, unit.DischargeStatusListId); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } /// /// Updates the and fields of the unit identified by . /// Returns the updated unit document, or null if no unit with the specified id exists in the collection. /// /// The unique identifier of the unit to update. /// The new name to assign to the unit. /// The new title to assign to the unit. /// The updated after the modification, or null if no matching unit was found. public async Task UpdateUnitInfo(ObjectId unitId, string name, string title) { var filter = Builders.Filter.Eq("_id", unitId); var update = Builders.Update //.Set(c => c.Id, unit.Id) .Set(c => c.Title, title) .Set(c => c.Name, name); return await Collection.FindOneAndUpdateAsync(filter, update, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } /// /// Updates the master list references on a document identified by the supplied id, /// applying the provided MasterListId values to the appropriate fields based on each entry's /// . Returns the updated when at least one update is applied, /// or null when no valid master list entries are provided. /// /// The DTO containing the target UnitId and the collection of master list entries to update. /// The updated after the modifications, or null if no updates were performed. public async Task UpdateUnitMasterList(UpdateUnitIdListDto updateUnitListDto) { var filter = Builders.Filter.Eq("_id", updateUnitListDto.UnitId); var update = Builders.Update; var updates = new List>(); foreach (var masterListData in updateUnitListDto.MasterListData) if (masterListData.MasterListType.HasValue) switch (masterListData.MasterListType.Value) { case MasterListType.AltableOptionList: updates.Add(update.Set(u => u.AltableOptionListId, masterListData.MasterListId)); break; case MasterListType.AllergyList: updates.Add(update.Set(u => u.AllergyListId, masterListData.MasterListId)); break; case MasterListType.DestinationList: updates.Add(update.Set(u => u.DestinationListId, masterListData.MasterListId)); break; case MasterListType.DiagnosisList: updates.Add(update.Set(u => u.DiagnosisListId, masterListData.MasterListId)); break; case MasterListType.DischargeStatusList: updates.Add(update.Set(u => u.DischargeStatusListId, masterListData.MasterListId)); break; case MasterListType.DoctorList: updates.Add(update.Set(u => u.DoctorListId, masterListData.MasterListId)); break; case MasterListType.DoctorTypeList: updates.Add(update.Set(u => u.DoctorTypeListId, masterListData.MasterListId)); break; case MasterListType.InternalDestinationList: updates.Add(update.Set(u => u.InternalDestinationListId, masterListData.MasterListId)); break; case MasterListType.InsulationList: updates.Add(update.Set(u => u.InsulationListId, masterListData.MasterListId)); break; case MasterListType.LanguageBarrierList: updates.Add(update.Set(u => u.LanguageBarrierListId, masterListData.MasterListId)); break; case MasterListType.PassiveSittingList: updates.Add(update.Set(u => u.PassiveSittingListId, masterListData.MasterListId)); break; case MasterListType.GenericList: updates.Add(update.Set(u => u.GenericListId, masterListData.MasterListId)); break; case MasterListType.MobilityOptionList: updates.Add(update.Set(u => u.MobilityOptionListId, masterListData.MasterListId)); break; case MasterListType.OriginList: updates.Add(update.Set(u => u.OriginListId, masterListData.MasterListId)); break; case MasterListType.PatientStatusList: updates.Add(update.Set(u => u.PatientStatusListId, masterListData.MasterListId)); break; case MasterListType.ProcedureList: updates.Add(update.Set(u => u.ProcedureListId, masterListData.MasterListId)); break; case MasterListType.TestList: updates.Add(update.Set(u => u.TestListId, masterListData.MasterListId)); break; case MasterListType.ServiceList: updates.Add(update.Set(u => u.ServiceListId, masterListData.MasterListId)); break; case MasterListType.TherapeuticCeilingList: updates.Add(update.Set(u => u.TherapeuticCeilingListId, masterListData.MasterListId)); break; case MasterListType.TreatmentList: updates.Add(update.Set(u => u.TreatmentListId, masterListData.MasterListId)); break; case MasterListType.VisitOptionList: updates.Add(update.Set(u => u.VisitOptionListId, masterListData.MasterListId)); break; case MasterListType.AccessControlList: updates.Add(update.Set(u => u.AccessControlListId, masterListData.MasterListId)); break; } if (updates.Any()) { var combinedUpdate = update.Combine(updates); return await Collection.FindOneAndUpdateAsync(filter, combinedUpdate, new FindOneAndUpdateOptions { ReturnDocument = ReturnDocument.After }); } return null; } /// /// Asynchronously updates the configuration of an existing unit identified by its ID. /// Returns true if the document was modified, or false if the update failed or no document matched. /// /// The of the unit whose configuration will be updated. /// The new to apply to the unit. /// A task that resolves to true when the update modified a document; otherwise false (including when the operation throws and the error is logged). public async Task UpdateConfiguration(ObjectId unitIdParsed, UnitConfiguration unitConfiguration) { var filter = Builders.Filter.Eq("_id", unitIdParsed); var update = Builders.Update .Set(c => c.Configuration, unitConfiguration); try { var result = await Collection.UpdateOneAsync(filter, update); return result.ModifiedCount > 0; } catch (Exception ex) { Log.Error("Error UpdateConfiguration from unit: {name}. Exception: {ex}", unitIdParsed, ex); return false; } } #endregion #region Delete /// /// Deletes the unit identified by the specified identifier, returning the deleted entity. If the operation fails, the exception is logged and null is returned. /// /// The identifier of the unit to delete. /// The deleted if found and removed; otherwise, null when an error occurs. public new async Task DeleteAsync(ObjectId id) { var filter = Builders.Filter.Eq(unit => unit.Id, id); try { return await Collection.FindOneAndDeleteAsync(filter); } catch (Exception e) { Log.Error(e.Message); return null; } } #endregion #endregion }