using adas_core.Application.Exceptions; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Enums; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.Filter; using adas_core.Domain.Models.Responses; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MongoDB.Bson; using MongoDB.Driver; namespace adas_core.Application.Services; public class MedicineService( IMedicineRepository medicineRepository, ITreatmentService treatmentService, IOptions apiSettings, ILogger logger, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService) : IMedicineService { private readonly List _notesIndicatingMedication = apiSettings.Value.NotesIndicatingMedication ?? []; /// /// Retrieves all medicines from the repository asynchronously. /// /// A task representing the asynchronous operation, containing a list of all entities. public async Task> GetAll() { return await medicineRepository.GetAll(); } /// /// Retrieves a medicine from the repository by its unique code, returning null if no matching medicine is found. /// /// The unique code identifier of the medicine to retrieve. /// A instance if a medicine with the specified code exists; otherwise, null. public async Task GetByCode(string code) { return await medicineRepository.GetMedicine(code); } /// /// Retrieves a list of medicines that match the provided codes or notes by delegating to the medicine repository. /// /// A list of strings representing the codes or notes used to search for matching medicines. /// A task that represents the asynchronous operation, containing the list of objects that match the specified codes or notes. public async Task> GetByCodeOrNote(List codeNote) { return await medicineRepository.GetMedicineByCodeOrNote(codeNote); } /// /// Retrieves a from the repository by its name. /// Returns null when no matching medicine is found. /// /// The name of the medicine to look up. /// A if a match is found; otherwise, null. public async Task GetByName(string name) { return await medicineRepository.GetMedicineByName(name); } /// /// Retrieves the medicines associated with a collection of patient treatments, resolving each treatment's requested give codes against the medicine repository. Falls back to parental nutrition calculation when no medicine is found, and to note-based detection when notes indicate medication; medicines of type "Nutrition" returned by the repository are excluded except when produced through the parental nutrition path. /// /// The patient treatments to resolve medicines for. Null entries are skipped. /// An enumerable of medicines resolved from the supplied treatments, aggregating types, codes, groups, and notes when a treatment specifies a RequestedGiveTreatment name. public async Task> GetMedicinesOfTreatments(IEnumerable treatments) { var totalMedicines = new List(); try { foreach (var treatment in treatments) { if (treatment == null) continue; var medicines = new List(); foreach (var code in treatment.RequestedGiveCodes) { var medicineList = await medicineRepository.GetMedicineByCodeOrNote([code.Identifier]); var medicine = medicineList.FirstOrDefault(); //Cant retrieve from medicine list check for parental nutrition and if is not and any note indicate that is medication it is added //to collection if (medicine == null) { var parentalNutritionMedicine = await CalculateParentalNutritionMedicine(treatment); if (parentalNutritionMedicine != null) { medicines.Add(parentalNutritionMedicine); continue; } var isMedicine = treatment.Notes.Any(n => _notesIndicatingMedication.Contains(n.Comment)); if (isMedicine) medicines.Add(new Medicine { Codes = [code.Identifier], Name = code.Text }); } else if (!medicine.Type.Contains("Nutrition")) { medicines.Add(medicine); } } if (!string.IsNullOrEmpty(treatment.RequestedGiveTreatment)) try { medicines = [ new Medicine { Name = treatment.RequestedGiveTreatment, Type = medicines.FindAll(t => t.Type.Any()) .Select(m => m.Type.Aggregate((x, y) => x + "," + y)).Distinct() .ToList(), Codes = medicines.FindAll(t => t.Codes.Any()) .Select(m => m.Codes.Aggregate((x, y) => x + "," + y)).Distinct() .ToList(), Group = medicines.FindAll(t => t.Group.Any()) .Select(m => m.Group.Aggregate((x, y) => x + "," + y)).Distinct() .ToList(), Notes = medicines.FindAll(t => t.Notes.Any()) .Select(m => m.Notes.Aggregate((x, y) => x + "," + y)).Distinct() .ToList() } ]; } catch (Exception ex) { logger.LogError("Error Getting Medicines from Treatment. {medicines} . Exception {ex}", string.Join(",", medicines), ex); } totalMedicines.AddRange(medicines); } } catch (Exception e) { logger.LogError("Error get Medicines Of Treatments: {eMessage} {eStackTrace}", e.Message, e.StackTrace); } return totalMedicines.AsEnumerable(); } /// /// Retrieves all active medicines associated with a patient by first resolving their active treatments and then collecting the medicines prescribed within them. /// /// The unique identifier of the patient whose active medicines are being queried. /// A task that represents the asynchronous operation, containing an enumerable collection of instances linked to the patient's active treatments. public async Task> GetActiveMedicinesByPatient(ObjectId patientId) { var activeTreatments = await treatmentService.GetActiveTreatmentsByPatient(patientId); return await GetMedicinesOfTreatments(activeTreatments); } /// /// Retrieves a paginated collection of medicines based on the specified pagination filter, including the total document count for the current query. /// /// The pagination filter containing the page number and page size used to determine which subset of medicines to return. /// A task that represents the asynchronous operation. The task result contains a with the medicines for the requested page, the current page number, the page size, and the total count of medicines matching the query. public async Task> GetPaginatedMedicines(PaginationFilter filter) { var result = medicineRepository.GetPaginatedMedicines(filter); var count = await result.CountDocumentsAsync(); var data = await result.Skip((filter.PageNumber - 1) * filter.PageSize) .Limit(filter.PageSize) .ToCursorAsync(); var dataList = await data.ToListAsync(); return new PaginationResponse(dataList, filter.PageNumber, filter.PageSize, count); } /// /// Retrieves a medicine by its unique identifier from the repository. /// Throws a when no medicine is found matching the provided identifier. /// /// The unique identifier of the medicine to retrieve. /// The entity if found; otherwise, the method throws an exception. /// Thrown when no medicine is found for the specified . public async Task GetMedicineById(ObjectId medicineId) { return await medicineRepository.GetMedicineById(medicineId) ?? throw new NotFoundException(HttpEnum.ErrorMessage.NotFoundResourceMissing); } /// /// Creates a new medicine record in the repository and records an audit log entry for the operation. Throws a conflict exception when the repository fails to produce a result. /// /// The medicine entity to create. /// The newly created entity. /// Thrown when the repository returns a null result, indicating the creation failed. public async Task PostMedicine(Medicine medicine) { var result = await medicineRepository.PostMedicine(medicine) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, result); return result; } /// /// Updates an existing medicine record and records the change in the audit log. /// /// The medicine entity containing the updated information. /// The updated , or null if the update did not produce a result. public async Task UpdateMedicine(Medicine medicine) { var oldMedicine = GetMedicineById(medicine.Id); var newMedicine = await medicineRepository.UpdateMedicine(medicine); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, newMedicine); return newMedicine; } /// /// Deletes a medicine identified by its unique identifier and records the operation in the audit log. /// Captures the existing medicine state prior to deletion to preserve audit trail details. /// /// The unique identifier of the medicine to delete. public async Task DeleteMedicineById(ObjectId medicineId) { var oldMedicine = GetMedicineById(medicineId); await medicineRepository.DeleteMedicineById(medicineId); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldMedicine, null); } /// /// Asynchronously retrieves all distinct medicine types from the repository. Returns an empty list if an exception occurs during the retrieval process. /// /// A task representing the asynchronous operation, containing a list of distinct medicine type strings, or an empty list if an error occurs. public async Task> GetAllTypes() { try { var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("type").ToListAsync(); var result = bsonDocuments.Select(doc => doc["type"].AsString).ToList(); return result; } catch (Exception) { return []; } } /// /// Asynchronously retrieves all distinct "group" values from the medicine repository and returns them as a list of strings. /// Returns an empty list if an error occurs while fetching or converting the data. /// /// A task that represents the asynchronous operation, containing a list of distinct group strings, or an empty list if an exception is encountered. public async Task> GetAllGroups() { try { var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("group").ToListAsync(); var result = bsonDocuments.Select(doc => doc["group"].AsString).ToList(); return result; } catch (Exception) { return []; } } /// /// Retrieves all distinct medicine names from the repository asynchronously, returning an empty list if any error occurs during the data retrieval or mapping process. /// /// A task that represents the asynchronous operation. The task result contains a list of medicine names, or an empty list if the operation fails. public async Task> GetAllNames() { try { var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("name").ToListAsync(); var result = bsonDocuments.Select(doc => doc["name"].AsString).ToList(); return result; } catch (Exception) { return []; } } /// /// Asynchronously retrieves all distinct code values from the medicine repository. /// Returns an empty list if an exception occurs during retrieval. /// /// A task representing the asynchronous operation, containing a list of code strings, or an empty list if an error occurs. public async Task> GetAllCodes() { try { var bsonDocuments = await medicineRepository.GetDistinctFieldDataQuery("codes").ToListAsync(); var result = bsonDocuments.Select(doc => doc["codes"].AsString).ToList(); return result; } catch (Exception) { return []; } } //Exclusive for H12O, not in calculatedObservations of H12O to dont repeat code in multiple places. /// /// Calculates a representing the parental nutrition for a patient treatment, returning null when the treatment does not contain a note with the "NPT" comment. /// The medicine name is taken from the first note with the "formularybaseformulation" comment type, defaulting to "UNKNOWN" when absent, and its type is set to ParenteralNutritionLipids when a neonatal lipids note is present, otherwise ParenteralNutrition. /// /// The patient treatment whose notes are used to derive the parental nutrition medicine. /// A task containing the calculated , or null when no "NPT" note is found or when an error is logged during processing. private Task CalculateParentalNutritionMedicine(PatientTreatment treatment) { Medicine? medicine = null; try { if (treatment is { Notes: not null } && treatment.Notes.FirstOrDefault(n => n.Comment == "NPT") != null) { medicine = new Medicine { Name = treatment.Notes.FirstOrDefault(n => n.CommentType == "formularybaseformulation")?.Comment ?? "UNKNOWN" }; if (treatment.Notes is { Count: > 0 }) medicine.Notes = treatment.Notes.Select(t => t.Comment).ToList(); if (treatment.Notes.FirstOrDefault(n => n.Comment is "LÍPIDOS NEONATALES AL 20%" or "LÍPIDOS NEONATALES AL 20% CON...") != null) medicine.Type = [MedicineEnum.Types.ParenteralNutritionLipids.ToString()]; else medicine.Type = [MedicineEnum.Types.ParenteralNutrition.ToString()]; } } catch (Exception e) { logger.LogError("Error calculate Parental Nutrition Medicine: {eMessage} {eStackTrace}", e.Message, e.StackTrace); } return Task.FromResult(medicine); } }