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

}