using adas_core.Application.Repositories.Interfaces; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Filter; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; using System.Text.RegularExpressions; namespace adas_core.Infrastructure.Repositories; /// /// Repository implementation for managing entities in MongoDB. /// Provides CRUD operations, search, pagination, and aggregation capabilities specific to medicines. /// public class MedicineRepository : MongoRepository, IMedicineRepository { private readonly ApiSettings _apiSettings; /// /// Initializes a new instance of the class. /// /// The application API settings containing configuration values, including the collection name. Cannot be . /// The MongoDB database instance used to access the collection. /// Thrown when is . public MedicineRepository(IOptions apiSettings, IMongoDatabase database) : base(database) { if (apiSettings == null) throw new ArgumentNullException(nameof(apiSettings)); _apiSettings = apiSettings.Value; } //For testing /// /// Gets the name of the MongoDB collection used to store medicines. /// Falls back to the default "medicines" collection name when not configured in the API settings. /// /// The collection name retrieved from the API settings, or "medicines" if not configured. public override string GetCollectionName() { return _apiSettings.Medicines ?? "medicines"; } /// /// Asynchronously retrieves a medicine whose Codes or Notes collection contains the specified code. /// /// The code or note text to search for within the medicine's codes or notes. /// /// A representing the asynchronous operation. /// The task result contains the first matching the criteria, or if no match is found. /// public async Task GetMedicine(string code) { var result = await Collection.FindAsync(x => x.Codes.Contains(code) || x.Notes.Contains(code)); return await result.FirstOrDefaultAsync(); } /// /// Asynchronously retrieves all medicines whose Codes array contains any of the specified code values. /// /// A list of code strings to match against the medicine's codes. At least one code must match. /// /// A representing the asynchronous operation. /// The task result contains a list of matching objects. Returns an empty list if no matches are found. /// public async Task> GetMedicineByCodeOrNote(List codeNotes) { var filter = Builders.Filter.AnyIn("Codes", codeNotes.ToArray()); var result = await Collection.FindAsync(filter); return result.ToList(); } /// /// Asynchronously retrieves a medicine by its exact name. /// /// The exact name of the medicine to search for. /// /// A representing the asynchronous operation. /// The task result contains the if found; otherwise, . /// public async Task GetMedicineByName(string name) { var filter = Builders.Filter.Eq(p => p.Name, name); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Asynchronously retrieves all medicines stored in the collection. /// /// /// A representing the asynchronous operation. /// The task result contains a list of all objects. Returns an empty list if the collection is empty. /// public async Task> GetAll() { var result = await Collection.FindAsync(_ => true); return result.ToList(); } /// /// Asynchronously retrieves a medicine by its unique identifier. /// /// The of the medicine to retrieve. /// /// A representing the asynchronous operation. /// The task result contains the if found; otherwise, . /// public async Task GetMedicineById(ObjectId medicineId) { var filter = Builders.Filter.Eq(p => p.Id, medicineId); var result = await Collection.FindAsync(filter); return await result.FirstOrDefaultAsync(); } /// /// Asynchronously inserts a new medicine into the database and returns the inserted entity (looked up by name). /// /// The instance to insert. /// /// A representing the asynchronous operation. /// The task result contains the newly inserted retrieved by its name, or if the lookup fails. /// public async Task PostMedicine(Medicine medicine) { await Collection.InsertOneAsync(medicine); var result = await Collection.FindAsync(v => v.Name == medicine.Name); return await result.FirstOrDefaultAsync(); } /// /// Asynchronously updates an existing medicine in the database and returns the updated entity. /// /// The instance containing the updated values. The is used to identify the document. /// /// A representing the asynchronous operation. /// The task result contains the same instance that was passed in, after the update operation has been issued. /// public async Task UpdateMedicine(Medicine medicine) { await UpdateOneAsync(medicine.Id, medicine); return medicine; } /// /// Asynchronously deletes a medicine from the database by its unique identifier. /// /// The of the medicine to delete. /// A representing the asynchronous delete operation. public async Task DeleteMedicineById(ObjectId medicineId) { var filter = Builders.Filter.Eq(po => po.Id, medicineId); await Collection.DeleteOneAsync(filter); } /// /// Retrieves a paginated, sorted, and filtered set of medicines based on the provided pagination filter. /// Results are sorted ascending by name. Supports optional case-insensitive regex match on the medicine name, /// and exact matches on code, type, and group. /// /// The containing pagination and filtering criteria. /// /// An instance that can be used to further refine and execute the query. /// When no filters are provided, all medicines are returned sorted by name. /// public IFindFluent GetPaginatedMedicines(PaginationFilter filter) { // Crear variable con la clase que construye los filtros que necesitamos var filterBuilder = Builders.Filter; // Crear una lista de filtros que pueden venir de tu servicio var filters = new List>(); // Ordenar los resultados por "time" en orden descendente var sort = Builders.Sort.Ascending("name"); if (filter.FilteredRequest != null) { var requestFilter = filter.FilteredRequest; if (!string.IsNullOrWhiteSpace(requestFilter.MedicineName)) { var escapedTextFilter = Regex.Escape(requestFilter.MedicineName); filters.Add(filterBuilder.Regex(m => m.Name, new BsonRegularExpression(escapedTextFilter, "i"))); } if (!string.IsNullOrWhiteSpace(requestFilter.MedicineCode)) filters.Add(filterBuilder.AnyEq(m => m.Codes, requestFilter.MedicineCode)); if (!string.IsNullOrWhiteSpace(requestFilter.MedicineType)) filters.Add(filterBuilder.AnyEq(m => m.Type, requestFilter.MedicineType)); if (!string.IsNullOrWhiteSpace(requestFilter.MedicineGroup)) filters.Add(filterBuilder.AnyEq(m => m.Group, requestFilter.MedicineGroup)); } if (filters.Count == 0) return Collection.Find(_ => true).Sort(sort); var combinedFilter = Builders.Filter.And(filters); return Collection .Find(combinedFilter) .Sort(sort); } /// /// Builds an aggregation pipeline that retrieves the distinct values of a specified array field /// from all medicines. The pipeline unwinds the field, groups by its value, sorts alphabetically, /// and projects the result with the original field name. /// /// The name of the array field on the document to retrieve distinct values for (for example, "Codes", "Type", or "Group"). /// /// An representing the aggregation pipeline that, when executed, /// yields documents containing the distinct values of the specified field. /// public IAggregateFluent GetDistinctFieldDataQuery(string field) { return Collection.Aggregate() .Unwind(field) .Group(new BsonDocument { { "_id", $"${field}" } }) .Sort(new BsonDocument { { "_id", 1 } }) .Project(new BsonDocument { { field, "$_id" }, { "_id", 0 } }); } /// /// Retrieves all distinct medicine groups available in the collection. /// /// /// A representing the asynchronous operation, /// containing a list of distinct group names as strings. /// /// This method is not yet implemented. public Task> GetAllGroups() { throw new NotImplementedException(); } }