using System.Collections.Generic;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
namespace GB5Shared.GOP.Qualifier
{
// ============================================================
// IQualifierCacheInvalidator
// ============================================================
public interface IQualifierCacheInvalidator
{
///
/// Removes the compiled plan snapshot from L2 IMemoryCache
/// for the specified qualifier + tenant combination.
/// Called after ActivateQualifierVersion commits.
///
void InvalidateQualifier(int qualifierId, int clientId);
///
/// Removes all cached plans for a given tenant client.
///
void InvalidateAllForClient(int clientId);
/// Registers a cache key so tenant-wide invalidation can reach it.
void Register(int qualifierId, int clientId);
}
// ============================================================
// QualifierCacheInvalidator — IMemoryCache implementation
//
// Cache keys follow the pattern:
// "QualifierPlan:{clientId}:{qualifierId}"
//
// Since IMemoryCache has no tag-based eviction, tenant-wide
// invalidation tracks registered keys in a ConcurrentDictionary
// keyed by clientId.
// ============================================================
public class QualifierCacheInvalidator : IQualifierCacheInvalidator
{
private readonly IMemoryCache _Cache;
private readonly ILogger _Logger;
// clientId → set of cache keys registered for that client
private readonly System.Collections.Concurrent.ConcurrentDictionary>
_KeysByClient = new();
public QualifierCacheInvalidator(
IMemoryCache cache,
ILogger logger)
{
_Cache = cache;
_Logger = logger;
}
public void InvalidateQualifier(int qualifierId, int clientId)
{
var key = PlanKey(qualifierId, clientId);
_Cache.Remove(key);
if (_KeysByClient.TryGetValue(clientId, out var keys))
keys.Remove(key);
_Logger.LogInformation(
"QualifierCache: invalidated plan for qualifier {QualifierId} client {ClientId}",
qualifierId, clientId);
}
public void InvalidateAllForClient(int clientId)
{
if (!_KeysByClient.TryRemove(clientId, out var keys)) return;
foreach (var key in keys)
_Cache.Remove(key);
_Logger.LogInformation(
"QualifierCache: invalidated {Count} plan entries for client {ClientId}",
keys.Count, clientId);
}
/// Registers a cache key so tenant-wide invalidation can reach it.
public void Register(int qualifierId, int clientId)
{
var keys = _KeysByClient.GetOrAdd(clientId, _ => []);
lock (keys) keys.Add(PlanKey(qualifierId, clientId));
}
public static string PlanKey(int qualifierId, int clientId)
=> $"QualifierPlan:{clientId}:{qualifierId}";
}
}