using System.Collections.Concurrent; using System.Security.Cryptography; using System.Text; namespace TaxBaik.Web.Services; internal static class TelegramAlertGate { private sealed record GateEntry(DateTimeOffset WindowStart, int Count); private static readonly ConcurrentDictionary Gates = new(); public static bool ShouldSend(string category, string content, TimeSpan window, int maxPerWindow = 1) { if (string.IsNullOrWhiteSpace(category)) return false; var now = DateTimeOffset.UtcNow; var key = $"{category}:{Fingerprint(content)}"; while (true) { if (!Gates.TryGetValue(key, out var current)) { var initial = new GateEntry(now, 1); if (Gates.TryAdd(key, initial)) return true; continue; } if (now - current.WindowStart >= window) { var reset = new GateEntry(now, 1); if (Gates.TryUpdate(key, reset, current)) return true; continue; } if (current.Count >= maxPerWindow) return false; var incremented = current with { Count = current.Count + 1 }; if (Gates.TryUpdate(key, incremented, current)) return true; } } private static string Fingerprint(string content) { if (string.IsNullOrEmpty(content)) return "empty"; var normalized = content.Length > 1500 ? content[..1500] : content; var bytes = SHA256.HashData(Encoding.UTF8.GetBytes(normalized)); return Convert.ToHexString(bytes); } }