using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using MyAi.Data;
using System.Collections.Concurrent;

namespace MyAi.Data.Services;

/// <summary>
/// Singleton implementation of <see cref="ITemplateService"/> that caches all templates from the
/// <c>myAi.Templates</c> table and refreshes them every 10 minutes.
/// Uses <see cref="IServiceScopeFactory"/> to resolve the scoped DbContext from a singleton lifetime.
/// </summary>
public sealed class DbTemplateService : ITemplateService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<DbTemplateService> _logger;
    private ConcurrentDictionary<string, string> _cache = new(StringComparer.OrdinalIgnoreCase);
    private DateTime _loadedAt = DateTime.MinValue;
    private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(10);

    public DbTemplateService(IServiceScopeFactory scopeFactory, ILogger<DbTemplateService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    /// <inheritdoc />
    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;
    }

    /// <inheritdoc />
    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;
    }

    /// <summary>
    /// Reloads all templates from the database when the cache TTL has expired.
    /// Swaps the cache atomically; logs an error and continues serving the stale cache on failure.
    /// </summary>
    private void EnsureCacheLoaded()
    {
        if (DateTime.UtcNow - _loadedAt < CacheTtl) return;

        try
        {
            using var scope = _scopeFactory.CreateScope();
            var db = scope.ServiceProvider.GetRequiredService<MyAiDbContext>();
            var rows = db.Templates.AsNoTracking().ToList();
            var fresh = new ConcurrentDictionary<string, string>(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.");
        }
    }

    /// <summary>Builds the dictionary key used in the cache.</summary>
    private static string CacheKey(string key, string language) => $"{key}::{language}";
}
