using AsbCloudApp.Comparators;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Services;
using AsbCloudDb;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.SAUB;


public class TelemetryUserService : ITelemetryUserService
{
    private const string CacheTag = "TelemetryUserCacheTag";
    private readonly TimeSpan CacheOlescence = TimeSpan.FromMinutes(5);

    private readonly IAsbCloudDbContext db;
    private readonly ITelemetryService telemetryService;
    private readonly IMemoryCache memoryCache;

    public TelemetryUserService(IAsbCloudDbContext db,
        ITelemetryService telemetryService,
        IMemoryCache memoryCache)
    {
        this.db = db;
        this.telemetryService = telemetryService;
        this.memoryCache = memoryCache;
    }

    public TelemetryUserDto? GetOrDefault(int idTelemetry, int idUser)
    {
        var entity = GetCache()
            .FirstOrDefault(u => u.IdTelemetry == idTelemetry && u.IdUser == idUser);

        if(entity is null)
            return null;

        return Convert(entity);
    }

    public IEnumerable<TelemetryUserDto> GetUsers(int idTelemetry, Func<TelemetryUserDto, bool>? predicate = null)
    {
        var entities = GetCache()
            .Where(u => u.IdTelemetry == idTelemetry);

        foreach (var entity in entities)
        {
            var dto = Convert(entity);
            if(predicate?.Invoke(dto)??true)
                yield return dto;
        }
        
        yield break;
    }

    public async Task UpsertAsync(string uid, IEnumerable<TelemetryUserDto> dtos, CancellationToken token = default)
    {
        if (!dtos.Any())
            return;

        var telemetry = telemetryService.GetOrCreateTelemetryByUid(uid);

        var entities = dtos.Distinct(new TelemetryUserDtoComparer()).Select(dto => {
            var entity = dto.Adapt<TelemetryUser>();
            entity.IdUser = dto.Id;
            entity.IdTelemetry = telemetry.Id;
            return entity;
        });
        var result = await db.Database.ExecInsertOrUpdateAsync(db.TelemetryUsers, entities, token);
        DropCache();
    }

    private IEnumerable<TelemetryUser> GetCache()
    {
        var cache = memoryCache.GetOrCreate(CacheTag, cacheEntry => {
            cacheEntry.AbsoluteExpirationRelativeToNow = CacheOlescence;
            cacheEntry.SlidingExpiration = CacheOlescence;

            var entities = db.Set<TelemetryUser>().ToArray();                
            return entities;
        });
        return cache!;
    }

    private void DropCache()
        => memoryCache.Remove(CacheTag);

    private static TelemetryUserDto Convert(TelemetryUser entity)
    {
        var dto = entity.Adapt<TelemetryUserDto>();
        dto.Id = entity.IdUser;
        return dto;
    }
}