namespace GrossesMitainesAPI.Services;
#region Dependencies
using GrossesMitainesAPI.Data;
using GrossesMitainesAPI.Models;
using Microsoft.EntityFrameworkCore;
#endregion
/// 
/// Service pour copier la BD localement au
/// démarrage de l'API.
/// 
public class DatabaseCacheService {
    #region DI
    private readonly IServiceScopeFactory _contextFactory; // https://entityframeworkcore.com/knowledge-base/51939451/how-to-use-a-database-context-in-a-singleton-service-
    private readonly ILogger _logger;
    #endregion
    #region Fields
    private Product[] _cache = new Product[1];
    private Dictionary _hits = new();
    private bool _ok = false, _needUpd = true;
    private PeriodicTimer _timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
    #endregion
    #region Ctor
    public DatabaseCacheService(ILogger logger, IServiceScopeFactory scopeFactory) {
        _contextFactory = scopeFactory;
        _logger = logger;
        _ok = UpdateCache();
        _needUpd = !_ok;
        UpdateJob();
    }
    #endregion
    #region Internal Methods
    private async void UpdateJob() {
        while (await _timer.WaitForNextTickAsync()) {
            if (_needUpd) {
                _ok = UpdateCache();
                _needUpd = !_ok;
            }
            if (_hits.Count > 0 && _ok)
                UpdateMetrics(); // les updates de metrics ne déclencheront pas d'update de cache
                                 // puisque les clients ne voient pas les métriques.
        }
    }
    private bool UpdateCache() {
        try {
            Product[] prods;
            using (var scope = _contextFactory.CreateScope()) {
                var db = scope.ServiceProvider.GetRequiredService();
                prods = db.Products.ToArray();
            }
            lock (_cache) {
                _cache = prods;
            }
        } catch (Exception e) {
            _logger.LogError(e, "Erreur de mise à jour de cache.");
            return false;
        }
        return true;
    }
    private bool UpdateMetrics() {
        try {
            Dictionary hits;
            lock (_hits) {
                hits = new(_hits);
                _hits.Clear();
            }
            List ids = hits.Keys.ToList();
            using (var scope = _contextFactory.CreateScope()) {
                var db = scope.ServiceProvider.GetRequiredService();
                List lst = db.Products.Where(x => ids.Contains((uint)x.Id)).ToList();
                foreach (var x in hits)
                    lst.First(x => x.Id == x.Id).Hits += x.Value;
                db.UpdateRange(lst);
                db.SaveChanges();
            }
        } catch (Exception e) {
            _logger.LogError(e, "Erreur de mise à jour de cache.");
            return false;
        }
        return true;
    }
    #endregion
    #region Public Methods
    public bool isOk() { return _ok; }
    public void askForRefresh() => _needUpd = true; 
    public void addHit(uint id) {
        lock (_hits) {
            if (_hits.ContainsKey(id))
                _hits[id] = _hits[id] + 1;
            else _hits[id] = 1;
        }
    }
    public Product[]? GetCacheCopy() {
        if (!_ok)
            return null;
        Product[] copy;
        try {
            lock (_cache) {
                copy = new Product[_cache.Length];
                _cache.CopyTo(copy, 0);
            }
        } catch (Exception e) {
            _logger.LogError(e, "Erreur de copie de cache.");
            return null;
        }
        return copy;
    }
    public IQueryable queryCache() {
        if (!_ok)
            return null;
        try {
            return _cache.AsQueryable();
        } catch (Exception e) {
            _logger.LogError(e, "Erreur de cache.");
            return null;
        }
    }
    #endregion
}