using adas_core.Application.Services.Interfaces; using adas_core.Domain.Models.GroupedObservations; using MongoDB.Bson; using System.Collections.Concurrent; namespace adas_core.Application.Services.Caching { /// /// Implementación de caché en memoria con soporte de locking seguro /// mediante LockManagerService + InMemoryLockProvider. /// Compatible con la interfaz ICacheService incluyendo GetOrSet. /// public class CacheService(LockManagerService lockManager) : ICacheService { private readonly ConcurrentDictionary _mem = new(); // HELPERS private static string BuildGroupedKey(GroupedField gf, ObjectId patientId) => $"GroupedObs:{patientId}:{gf.Name}"; // GET OR SET (string key) public async Task GetOrSetObjectAsync( string key, Func> factory, TimeSpan? ttl = null, CancellationToken cancellationToken = default) { // FAST PATH if (_mem.TryGetValue(key, out var existing)) return (T)existing; // LOCKED PATH return await lockManager.WithLockAsync( $"getorset:{key}", TimeSpan.FromSeconds(5), async () => { if (_mem.TryGetValue(key, out var again)) return (T)again; var created = await factory(); if (created != null!) _mem[key] = created; return created!; }, cancellationToken); } public async Task GetOrSetValueAsync( string key, Func> loader, TimeSpan? ttlOverride = null) { var result = await GetOrSetObjectAsync(key, loader, ttlOverride); return (string?)result; } // GET OR SET (GroupedField + patientId) public async Task GetOrSetObjectAsync( GroupedField groupedField, ObjectId patientId, Func> factory, TimeSpan? ttl = null, CancellationToken cancellationToken = default) { var key = BuildGroupedKey(groupedField, patientId); if (_mem.TryGetValue(key, out var existing)) return (T)existing; return await lockManager.WithLockAsync( $"getorset:{key}", TimeSpan.FromSeconds(5), async () => { if (_mem.TryGetValue(key, out var again)) return (T)again; var created = await factory(); if (created != null!) _mem[key] = created; return created!; }, cancellationToken); } // GET / SET public void SetValue(string key, string value) => _mem[key] = value; public string? GetValue(string key) => _mem.TryGetValue(key, out var v) ? v.ToString() : null; public Task GetObjectAsync(string key, bool updateExpiration = true) { return Task.FromResult( _mem.TryGetValue(key, out var v) ? (T?)v : default ); } public Task SetObjectAsync(string key, T obj, bool updateExpiration = true) { _mem[key] = obj!; return Task.CompletedTask; } public Task GetObjectAsync(string key, TimeSpan? ttlOverride, bool upd) => GetObjectAsync(key, upd); public Task SetObjectAsync(string key, T obj, TimeSpan? ttlOverride, bool upd) => SetObjectAsync(key, obj, upd); // DELETE / CLEAN public Task DeleteObjectAsync(string key) { _mem.TryRemove(key, out _); return Task.CompletedTask; } public Task DeleteByPatternAsync(string pattern) { var p = pattern.Replace("*", ""); var keys = _mem.Keys.Where(k => k.Contains(p)).ToList(); long removed = 0; foreach (var k in keys) if (_mem.TryRemove(k, out _)) removed++; return Task.FromResult(removed); } public void CleanCache() => _mem.Clear(); } }