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;
///
/// Implements to manage configuration units,
/// using for data persistence,
/// for API configuration values,
/// and for diagnostic logging.
///
///
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;
}
}
}