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.MongoModels; using adas_core.Domain.Models.Pumps; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace adas_core.Application.Services; /// /// Implements the contract to manage configuration operations for pumps. /// /// /// This service relies on an for data access, an for API configuration, /// an for diagnostics, an for HTTP context retrieval, and an for auditing operations. /// /// public class ConfigPumpsService( IConfigPumpsRepository configPumpsRepository, IOptions apiSettings, ILogger logger, IHttpContextAccessor httpContextAccessor, ILocalAuditService auditService) : IConfigPumpsService { private static ConfigPumps? _config; private static DateTime _nextRefresh = DateTime.MinValue; private readonly bool _configPumpsRequired = apiSettings.Value.ConfigPumpsRequired; private readonly string _key = apiSettings.Value.ConfigPumpsKey ?? "PV1"; private readonly int? _refreshTimeout = apiSettings.Value.ConfigObservation?.Refresh; /// /// Maps a using its alarm type configuration. Returns the original observation unchanged when configuration-based pump mapping is not required or when no matching configuration is found. /// /// The pump observation to map, containing the alarm type used for configuration lookup. /// The mapped pump observation, or the original observation when mapping is skipped or the configuration lookup yields no result. /// public async Task Map(PumpObservation obs) { if (!_configPumpsRequired) return obs; var conf = !string.IsNullOrEmpty(obs.AlarmType.ToString()) ? await Get(obs.AlarmType.ToString() ?? string.Empty) : null; if (conf == null) return obs; return await MapConf(obs, conf); } /// /// Retrieves all available pump configurations from the underlying repository. /// /// A task that represents the asynchronous operation, containing a list of if any are available, or null when no configurations exist. /// public async Task?> GetAllPumpConfigs() { return await configPumpsRepository.GetAllConfigs(); } /// /// Retrieves the configuration for a pump identified by its unique identifier from the configuration repository. /// Returns null when no matching pump configuration is found. /// /// The unique identifier of the pump configuration to retrieve. /// A instance if a matching configuration is found; otherwise, null. /// public async Task GetPumpConfigById(string id) { return await configPumpsRepository.FindById(id); } /// /// Retrieves the list of configuration items associated with the config pump identified by the given identifier. Returns null when no matching config pump is found in the repository. /// /// The unique identifier of the config pump to look up. /// A task that resolves to the list of entries, or null if the config pump does not exist. /// public async Task?> GetConfigItems(string id) { var result = await configPumpsRepository.FindById(id); return result?.Items; } /// /// Updates the pump configuration in the repository and records an audit log of the change. /// Throws a if the update operation returns null. /// /// The pump configuration to update, identified by its . /// A task representing the asynchronous operation, containing the updated . /// Thrown when the update operation fails. /// public async Task UpdatePumpConfig(ConfigPumps pumpConfig) { var oldPumpConfig = configPumpsRepository.FindById(pumpConfig.Id); var newPumpConfig = await configPumpsRepository.UpdateConfig(pumpConfig) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictUpdateFailed); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, oldPumpConfig, newPumpConfig); return newPumpConfig; } /// /// Inserts a new pump configuration into the repository, records an audit log entry for the operation, and returns the inserted configuration. If the configuration cannot be retrieved after insertion or any exception occurs, the error is logged and the method returns null. /// /// The pump configuration to insert. /// The inserted on success; otherwise, null when an error occurs during the operation. /// Thrown when the inserted configuration cannot be found in the repository after insertion. /// public async Task InsertPumpConfig(ConfigPumps pumpConfig) { try { await configPumpsRepository.InsertOneAsync(pumpConfig); var newPumpConfig = await configPumpsRepository.FindById(pumpConfig.Id) ?? throw new ConflictException(HttpEnum.ErrorMessage.ConflictCreationFailed); await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, null, newPumpConfig); return newPumpConfig; } catch (Exception ex) { logger.LogError("ERROR inserting config_pumps: {key}. Exception: {exMessage} ", pumpConfig.Id, ex.Message); return null; } } /// /// Deletes a pump configuration asynchronously, auditing the change on success and returning false if the repository operation reports an error or an exception is thrown. /// /// The instance representing the pump configuration to delete. /// A task that resolves to true when the configuration is deleted and the audit log is recorded; false when the delete operation fails or an exception occurs. /// public async Task DeletePumpConfig(ConfigPumps config) { try { var result = await configPumpsRepository.DeleteConfig(config); if (result) { logger.LogError("ERROR deleting config_pumps: {key}. ", config.Id); return false; } await auditService.CreateAuditLogAsync(httpContextAccessor.HttpContext?.User!, config, null); return true; } catch (Exception ex) { logger.LogError("ERROR deleting config_pumps: {key}. Exception: {exMessage} ", config.Id, ex.Message); return false; } } /// /// Determines the retention actions to apply for a pump observation based on its configuration. /// When a retention policy is configured, returns the configured policy and its value; otherwise, falls back to with a null value. /// /// The pump observation for which to evaluate the retention policy. /// A task containing the retention result with the applicable policy and associated value, or a default NoDelete result when no policy is configured. /// public async Task RetentionActions(PumpObservation obs) { //TODO sacarlo de la configuración específica de Bombas var conf = await Get(obs); return conf is { RetentionPolicy: not null } ? new ObservatitonRetentionResult(conf.RetentionPolicy.Value, conf.RetentionPolicyValue) : new ObservatitonRetentionResult(RetentionPolicy.NoDelete, null); } /// /// Maps the UI configuration from a onto a , assigning the configuration only when it is provided and non-empty. /// /// The pump observation that will receive the UI configuration. /// The configuration source whose UI configuration is applied to when present. /// A completed containing the updated observation. /// private static Task MapConf(PumpObservation obs, ConfigPumpItem conf) { if (conf.UiConfiguration != null && conf.UiConfiguration.Count != 0) obs.UiConfiguration = conf.UiConfiguration; return Task.FromResult(obs); } /// /// Retrieves a matching the specified alarm type by parsing the input string into a and searching the configuration items. /// /// The string representation of the alarm type to look up; if it cannot be parsed into a valid , the method returns null. /// A whose AlarmType matches the parsed value, or null if parsing fails or no matching item is found. /// private async Task Get(string alarmType) { if (!Enum.TryParse(alarmType, out PumpEnum.AlarmType alarmTypeParsed)) return null; var result = await GetConfig(); return result?.Items?.FirstOrDefault(i => i.AlarmType == alarmTypeParsed); } /// /// Retrieves the configuration item associated with the specified pump observation by matching its message type against the loaded configuration entries. /// Returns null when the configuration, its items collection, or a matching entry is not found. /// /// The pump observation whose MessageType is used to locate the corresponding configuration entry. /// A matching the observation's message type, or null if the configuration is unavailable or no matching item exists. /// private async Task Get(PumpObservation pobs) { var config = await GetConfig(); return config?.Items?.FirstOrDefault(i => i.Type == pobs.MessageType); } /// /// Asynchronously retrieves the list of configuration pump items by fetching the current configuration. /// Returns a null list if the underlying configuration is not available. /// /// A task containing a list of objects, or null if the configuration could not be retrieved. /// public async Task?> Get() { var result = await GetConfig(); return result?.Items; } /// /// Retrieves the configuration associated with the current key, returning a cached value when it has not yet expired. /// Falls back to fetching from the repository when the cache is missing or stale, and returns null if an error occurs during retrieval. /// /// A task containing the instance if available; otherwise, null when the repository lookup fails. /// private async Task GetConfig() { try { if (_config != null && DateTime.Now <= _nextRefresh) return _config; _config = await configPumpsRepository.FindById(_key); _nextRefresh = _refreshTimeout.HasValue ? DateTime.Now.AddSeconds(_refreshTimeout.Value) : DateTime.MinValue; return _config; } catch (Exception ex) { logger.LogError("ERROR READ config_pumps: {key}. Exception: {exMessage} ", _key, ex.Message); return null; } } }