using adas_core.Application.Services.Interfaces;
using adas_core.Domain.Enums;
using adas_core.Domain.Models.AppSettings;
using adas_core.Domain.Models.GroupedObservations;
using adas_core.Domain.Utils;
using MongoDB.Bson;
namespace adas_core.Application.Services.Caching
{
///
/// Orquestador de caché. Selecciona el backend (Redis, InMemory, None)
/// según CacheSettings y la entidad del key.
/// Implementa ICacheService y delega en el backend elegido.
///
public class CacheDispatcher(
RedisService redis,
CacheService memory,
NoCacheService noop,
CacheSettings cacheSettings)
: ICacheService
{
// Selección de backend
///
/// Selects the appropriate cache backend (Redis, in-memory, or no-op) for the given key by classifying the key into an entity type and resolving its configured cache mode.
/// Unknown entity types default to in-memory caching, and unrecognized modes fall back to the no-op cache service.
///
/// The cache key used to determine the entity type and the corresponding cache backend.
/// The instance that should handle caching for the supplied key.
private ICacheService SelectBackend(string key)
{
var entity = CacheKeyClassifier.Classify(key);
var mode = entity switch
{
CacheEnum.EntityType.Patients => cacheSettings.Patients,
CacheEnum.EntityType.Displays => cacheSettings.Displays,
CacheEnum.EntityType.PumpObservations => cacheSettings.PumpObservations,
CacheEnum.EntityType.Appointments => cacheSettings.Appointments,
CacheEnum.EntityType.GroupedObservations => cacheSettings.GroupedObservations,
_ => CacheEnum.Mode.Cache
};
return mode switch
{
CacheEnum.Mode.Redis => redis,
CacheEnum.Mode.Cache => memory,
_ => noop
};
}
// Para GroupedObservations generamos la misma clave compuesta que el resto de servicios,
// de modo que el clasificador y la política de TTL funcionen igual.
///
/// Builds a composite key used to identify grouped observations for a specific patient.
///
/// The grouped field whose name contributes to the key.
/// The identifier of the patient associated with the grouped observation.
/// A formatted key string in the form GroupedObs:{patientId}:{gf.Name}.
private static string BuildGroupedKey(GroupedField gf, ObjectId patientId)
=> $"GroupedObs:{patientId}:{gf.Name}";
///
/// Selects the appropriate cache backend for the given grouped field and patient identifier by building a grouped key and resolving the backend through the key-based overload.
///
/// The grouped field used to derive the cache key.
/// The patient identifier used to derive the cache key.
/// The backend associated with the built grouped key.
private ICacheService SelectBackend(GroupedField groupedField, ObjectId patientId)
=> SelectBackend(BuildGroupedKey(groupedField, patientId));
// GetOrSet (KEY string)
///
/// Asynchronously retrieves the object associated with the specified key from the selected backend, or sets it using the provided factory if it is not already cached.
///
/// The cache key used to identify the object and to select the appropriate backend.
/// A delegate that asynchronously produces the value to store when the key is not present in the selected backend.
/// An optional time-to-live duration for the cached object. If null, the backend's default expiration is applied.
/// A token to observe while waiting for the operation to complete.
/// A task that represents the asynchronous operation, containing the retrieved or newly created object of type .
public Task GetOrSetObjectAsync(
string key,
Func> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
=> SelectBackend(key).GetOrSetObjectAsync(key, factory, ttl, cancellationToken);
///
/// Asynchronously retrieves the value associated with the specified key from the backend selected for that key,
/// or loads and stores it using the provided loader function if it is not already present.
/// Supports an optional time-to-live (TTL) for the cached entry, and the returned value may be null.
///
/// The key used to identify the cached value and to select the appropriate backend.
/// An asynchronous function that produces the value to cache when no existing entry is found.
/// An optional time-to-live duration after which the cached entry expires. If null, the backend's default TTL is used.
/// A task that represents the asynchronous operation, containing the cached or loaded string value, or null if no value could be obtained.
public Task GetOrSetValueAsync(
string key,
Func> loader,
TimeSpan? ttl = null)
=> SelectBackend(key).GetOrSetValueAsync(key, loader, ttl);
// GetOrSet (GroupedField + PatientId)
///
/// Asynchronously retrieves the object associated with the specified grouped field and patient, or creates and stores it using the provided factory if it does not exist.
/// The appropriate backend is selected based on the grouped field and patient identifier before the underlying get-or-set operation is performed.
///
/// The type of the object to retrieve or create.
/// The grouped field that determines the target backend and identifies the cached object.
/// The identifier of the patient whose object is being retrieved or created.
/// The asynchronous factory used to create the object when no cached value is available.
/// An optional time-to-live applied to the cached object. When null, the backend's default expiration is used.
/// The token to observe for canceling the asynchronous operation.
/// A task that represents the asynchronous get-or-set operation, containing the retrieved or newly created object.
public Task GetOrSetObjectAsync(
GroupedField groupedField,
ObjectId patientId,
Func> factory,
TimeSpan? ttl = null,
CancellationToken cancellationToken = default)
=> SelectBackend(groupedField, patientId)
.GetOrSetObjectAsync(groupedField, patientId, factory, ttl, cancellationToken);
// Set/Get básicos
///
/// Sets the value associated with the specified key by selecting the appropriate backend for that key and delegating the assignment to it.
///
/// The key used to select the backend and identify the value to set.
/// The value to associate with the specified key.
public void SetValue(string key, string value)
=> SelectBackend(key).SetValue(key, value);
///
/// Retrieves the value associated with the specified key by delegating the lookup to a backend selected for that key.
///
/// The key used to select the backend and retrieve the associated value.
/// The value associated with the key, or null if the selected backend returns no value.
public string? GetValue(string key)
=> SelectBackend(key).GetValue(key);
///
/// Retrieves an object of type from the backend selected by the given key, with an option to trigger an update.
///
/// The identifier used to select the appropriate backend and to look up the object.
/// Indicates whether the underlying backend should perform an update during retrieval. Defaults to true.
/// A task that represents the asynchronous retrieval operation, containing the object of type or null if not found.
public Task GetObjectAsync(string key, bool upd = true)
=> SelectBackend(key).GetObjectAsync(key, upd);
///
/// Asynchronously stores an object in the backend selected by the specified key.
///
/// The type of the object to store.
/// The key used to select the backend and identify the stored object.
/// The object to store in the selected backend.
/// Indicates whether an update operation should be performed. Defaults to true.
/// A task that represents the asynchronous store operation.
public Task SetObjectAsync(string key, T obj, bool upd = true)
=> SelectBackend(key).SetObjectAsync(key, obj, upd);
///
/// Retrieves an object of the specified type from the backend selected by the given key, optionally applying a time-to-live and update behavior.
///
/// The identifier used to select the backend and locate the stored object.
/// An optional time-to-live applied to the object; if null, the backend's default is used.
/// A flag indicating whether the retrieval should update the object's state (e.g., refresh expiration).
/// A task containing the deserialized object, or null if the object is not found in the selected backend.
public Task GetObjectAsync(string key, TimeSpan? ttl, bool upd)
=> SelectBackend(key).GetObjectAsync(key, ttl, upd);
///
/// Asynchronously stores an object in the backend selected for the specified key, with an optional time-to-live and update flag.
///
/// The key used to select the target backend and identify the object.
/// The object to store.
/// The optional time-to-live duration for the stored object.
/// Indicates whether to update an existing entry or create a new one.
/// A task that represents the asynchronous set operation.
public Task SetObjectAsync(string key, T obj, TimeSpan? ttl, bool upd)
=> SelectBackend(key).SetObjectAsync(key, obj, ttl, upd);
///
/// Asynchronously deletes the object identified by the specified key by delegating the operation to the backend selected for that key.
///
/// The identifier of the object to delete, also used to resolve the responsible backend.
/// A task that represents the asynchronous delete operation.
public Task DeleteObjectAsync(string key)
=> SelectBackend(key).DeleteObjectAsync(key);
///
/// Deletes entries matching the specified pattern from both Redis and in-memory storage, returning the total count of deleted entries.
///
/// The pattern used to match entries for deletion in both storage backends.
/// The combined total number of entries deleted from Redis and in-memory storage.
public async Task DeleteByPatternAsync(string pattern)
=> await redis.DeleteByPatternAsync(pattern) + await memory.DeleteByPatternAsync(pattern);
///
/// Clears all cached data from both the in-memory cache and the Redis cache, ensuring that stale entries are removed across all configured cache providers.
///
public void CleanCache()
{
memory.CleanCache();
redis.CleanCache();
}
}
}