using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using MyAi.Models.Data; using System.Collections.Concurrent; namespace MyAi.Models.Services; public sealed class DbTemplateService : ITemplateService { private readonly IServiceScopeFactory _scopeFactory; private readonly ILogger _logger; private ConcurrentDictionary _cache = new(StringComparer.OrdinalIgnoreCase); private DateTime _loadedAt = DateTime.MinValue; private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(10); public DbTemplateService(IServiceScopeFactory scopeFactory, ILogger logger) { _scopeFactory = scopeFactory; _logger = logger; } public string Get(string key, string language = "en") { EnsureCacheLoaded(); if (_cache.TryGetValue(CacheKey(key, language), out var value)) return value; if (!string.Equals(language, "en", StringComparison.OrdinalIgnoreCase) && _cache.TryGetValue(CacheKey(key, "en"), out var fallback)) return fallback; _logger.LogWarning("Template not found: key={Key}, language={Language}", key, language); return key; } public string Render(string key, string language, params (string Key, string Value)[] placeholders) { var template = Get(key, language); foreach (var (k, v) in placeholders) template = template.Replace($"{{{{{k}}}}}", v, StringComparison.OrdinalIgnoreCase); return template; } private void EnsureCacheLoaded() { if (DateTime.UtcNow - _loadedAt < CacheTtl) return; try { using var scope = _scopeFactory.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var rows = db.Templates.AsNoTracking().ToList(); var fresh = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); foreach (var row in rows) fresh[CacheKey(row.Key, row.Language)] = row.Value; _cache = fresh; _loadedAt = DateTime.UtcNow; _logger.LogDebug("Template cache refreshed. {Count} templates loaded.", rows.Count); } catch (Exception ex) { _logger.LogError(ex, "Failed to refresh template cache. Serving stale cache."); } } private static string CacheKey(string key, string language) => $"{key}::{language}"; }