using System.Reflection; using adas_core.Application.Repositories.Interfaces; using adas_core.Application.Services.Interfaces; using adas_core.Domain.Models; using adas_core.Domain.Models.AppSettings; using adas_core.Domain.Models.MongoModels; using adas_core.Domain.Models.Pumps; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace adas_core.Application.Services; public class ConfigUnitsService( IConfigUnitsRepository configUnitsRepository, IOptions apiSettings, ILogger logger) : IConfigUnitsService { private static ConfigUnits? _config; private static DateTime _nextRefresh = DateTime.MinValue; private readonly bool _configUnitsRequired = apiSettings.Value.ConfigUnitsRequired; private readonly string _key = apiSettings.Value.ConfigUnitsKey ?? "PV1"; private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh; /// /// Maps a patient observation using the unit configuration when configuration units are required. /// If configuration units are not required, the observation's units are not specified, or no matching configuration is found, the original observation is returned unchanged. /// /// The patient observation to be mapped. /// The mapped observation when a matching unit configuration is resolved; otherwise, the original observation. public async Task Map(T obs) where T : BasePatientObservation { if (!_configUnitsRequired) return obs; var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null; return conf == null ? obs : MapConf(obs, conf); } /// /// Maps a by processing its pump value properties. /// If configuration units are not required, the observation is returned unchanged; otherwise, the pump values are mapped via MapPumpValues and the observation is returned. /// /// The to be mapped. /// The mapped , returned as-is when units configuration is not required or after pump value mapping otherwise. public async Task Map(PumpObservation obs) { if (!_configUnitsRequired) return obs; //Para cada propiedad de la observación que sea del tipo PumpValue llama a GetByCodeSysAndCode(PumpValue) await MapPumpValues(obs); // var conf = !string.IsNullOrEmpty(obs.Units) ? await Get(obs.Units) : null; // return conf == null ? obs : MapConf(obs, conf); return obs; } /// /// Recorre recursivamente las propiedades del objeto para encontrar y convertir PumpValues. /// private async Task MapPumpValues(object? targetObject) { if (targetObject == null) return; var properties = targetObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var property in properties) { var propertyValue = property.GetValue(targetObject); if (propertyValue == null) continue; if (property.PropertyType == typeof(CommonPumpTypes.PumpValue)) { var pumpValue = (CommonPumpTypes.PumpValue)propertyValue; if (string.IsNullOrEmpty(pumpValue.Units)) continue; var conf = await Get(pumpValue.Units); if (conf != null) pumpValue.Units = conf.Value; } // En este caso Syringe es una clase que tienen un pumpValue else if (property.PropertyType.IsClass && property.PropertyType != typeof(string)) { // Llamada recursiva para inspeccionar las propiedades anidadas await MapPumpValues(propertyValue); } } } /// /// Maps a configuration unit value onto a patient observation. If the observation is not a , the original observation is returned unchanged; otherwise, its Units property is assigned from the configuration unit item's value. /// /// The base patient observation to which the configured unit value will be applied. /// The configuration unit item whose Value is used as the unit to assign. /// The input observation, with Units set from when applicable, or the unchanged observation when it is not a . private T MapConf(T obs, ConfigUnitItem conf) where T : BasePatientObservation { if (obs is not PatientObservation pobs) return obs; logger.LogDebug("Mapping config Unit obs: {obs} to units: {conf}", obs, conf.Value); pobs.Units = conf.Value; return obs; } /// /// Retrieves a configuration unit item by its unique code from the configuration store. /// Returns null when the configuration cannot be loaded or when no item matches the specified code. /// /// The unique code identifier of the configuration unit item to look up. /// A task that represents the asynchronous operation. The task result contains the matching , or null if the configuration is unavailable or no item is found. public async Task Get(string code) { var result = await GetConfig(); return result?.Items?.FirstOrDefault(i => i.Code == code); } /// /// Retrieves the configuration associated with the current key, using a time-based cache /// to avoid repeated repository calls before the configured refresh timeout elapses. On any failure during the /// repository lookup, the error is logged and null is returned. /// /// /// A containing the cached or freshly fetched , or /// null if the repository lookup fails. /// private async Task GetConfig() { try { if (_config != null && DateTime.Now <= _nextRefresh) return _config; _config = await configUnitsRepository.FindById(_key); _nextRefresh = _refreshTimeout.HasValue ? DateTime.Now.AddSeconds(_refreshTimeout.Value) : DateTime.MinValue; return _config; } catch (Exception ex) { logger.LogError(ex, "ERROR READ config_units: {_key}: {ex}", _key, ex.Message); return null; } } }