using System; using System.Collections.Concurrent; using System.IO; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace MarketAlly.AIPlugin.DevOps.Performance { public class AnalysisCache { private readonly ConcurrentDictionary _cache; private readonly ILogger _logger; private readonly TimeSpan _defaultExpiry; public AnalysisCache(ILogger logger = null, TimeSpan? defaultExpiry = null) { _cache = new ConcurrentDictionary(); _logger = logger; _defaultExpiry = defaultExpiry ?? TimeSpan.FromHours(1); } public async Task GetOrSetAsync(string key, Func> factory, TimeSpan? expiry = null) where T : class { var cacheKey = GenerateCacheKey(key); var expiryTime = expiry ?? _defaultExpiry; if (_cache.TryGetValue(cacheKey, out var existingEntry)) { if (existingEntry.ExpiresAt > DateTime.UtcNow) { try { var cachedResult = JsonSerializer.Deserialize(existingEntry.Data); _logger?.LogDebug("Cache hit for key: {CacheKey}", cacheKey); return cachedResult; } catch (JsonException ex) { _logger?.LogWarning(ex, "Failed to deserialize cached data for key: {CacheKey}", cacheKey); _cache.TryRemove(cacheKey, out _); } } else { _cache.TryRemove(cacheKey, out _); _logger?.LogDebug("Cache entry expired for key: {CacheKey}", cacheKey); } } _logger?.LogDebug("Cache miss for key: {CacheKey}, executing factory", cacheKey); var result = await factory(); if (result != null) { await SetAsync(cacheKey, result, expiryTime); } return result; } public async Task GetAsync(string key) where T : class { var cacheKey = GenerateCacheKey(key); if (_cache.TryGetValue(cacheKey, out var entry)) { if (entry.ExpiresAt > DateTime.UtcNow) { try { var result = JsonSerializer.Deserialize(entry.Data); _logger?.LogDebug("Retrieved from cache: {CacheKey}", cacheKey); return result; } catch (JsonException ex) { _logger?.LogWarning(ex, "Failed to deserialize cached data for key: {CacheKey}", cacheKey); _cache.TryRemove(cacheKey, out _); } } else { _cache.TryRemove(cacheKey, out _); } } return null; } public async Task SetAsync(string key, T value, TimeSpan? expiry = null) { var cacheKey = GenerateCacheKey(key); var expiryTime = expiry ?? _defaultExpiry; try { var jsonData = JsonSerializer.Serialize(value, new JsonSerializerOptions { WriteIndented = false, PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); var entry = new CacheEntry { Data = jsonData, CreatedAt = DateTime.UtcNow, ExpiresAt = DateTime.UtcNow.Add(expiryTime) }; _cache.AddOrUpdate(cacheKey, entry, (k, v) => entry); _logger?.LogDebug("Cached data for key: {CacheKey}, expires at: {ExpiresAt}", cacheKey, entry.ExpiresAt); await Task.CompletedTask; } catch (Exception ex) { _logger?.LogError(ex, "Failed to cache data for key: {CacheKey}", cacheKey); } } public async Task GenerateFileBasedCacheKeyAsync(string filePath, string operation) { if (!File.Exists(filePath)) { return $"{operation}:{filePath}:notfound"; } try { var fileInfo = new FileInfo(filePath); var lastWriteTime = fileInfo.LastWriteTimeUtc.Ticks; var fileSize = fileInfo.Length; // Create a hash of file metadata for cache key var keyData = $"{operation}:{filePath}:{lastWriteTime}:{fileSize}"; using var sha256 = SHA256.Create(); var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyData)); var hashString = Convert.ToHexString(hashBytes)[..16]; // Use first 16 characters return $"{operation}:{hashString}"; } catch (Exception ex) { _logger?.LogWarning(ex, "Failed to generate file-based cache key for: {FilePath}", filePath); return $"{operation}:{filePath}:error"; } } public void InvalidateByPattern(string pattern) { var keysToRemove = new List(); foreach (var kvp in _cache) { if (kvp.Key.Contains(pattern, StringComparison.OrdinalIgnoreCase)) { keysToRemove.Add(kvp.Key); } } foreach (var key in keysToRemove) { _cache.TryRemove(key, out _); _logger?.LogDebug("Invalidated cache entry: {CacheKey}", key); } _logger?.LogInformation("Invalidated {Count} cache entries matching pattern: {Pattern}", keysToRemove.Count, pattern); } public void Clear() { var count = _cache.Count; _cache.Clear(); _logger?.LogInformation("Cleared {Count} cache entries", count); } public void CleanupExpired() { var now = DateTime.UtcNow; var expiredKeys = new List(); foreach (var kvp in _cache) { if (kvp.Value.ExpiresAt <= now) { expiredKeys.Add(kvp.Key); } } foreach (var key in expiredKeys) { _cache.TryRemove(key, out _); } if (expiredKeys.Count > 0) { _logger?.LogDebug("Cleaned up {Count} expired cache entries", expiredKeys.Count); } } public CacheStatistics GetStatistics() { var now = DateTime.UtcNow; var validEntries = 0; var expiredEntries = 0; foreach (var kvp in _cache) { if (kvp.Value.ExpiresAt > now) { validEntries++; } else { expiredEntries++; } } return new CacheStatistics { TotalEntries = _cache.Count, ValidEntries = validEntries, ExpiredEntries = expiredEntries, HitRate = 0 // Would need to track hits/misses for actual hit rate }; } private string GenerateCacheKey(string key) { using var sha256 = SHA256.Create(); var hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(key)); return Convert.ToHexString(hashBytes)[..16]; // Use first 16 characters for shorter keys } private class CacheEntry { public string Data { get; set; } public DateTime CreatedAt { get; set; } public DateTime ExpiresAt { get; set; } } } public class CacheStatistics { public int TotalEntries { get; set; } public int ValidEntries { get; set; } public int ExpiredEntries { get; set; } public double HitRate { get; set; } } }