using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Mapster;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Repository
{
    public class WitsRecordRepository<TDto, TEntity> : IWitsRecordRepository<TDto>
        where TEntity : AsbCloudDb.Model.WITS.RecordBase, ITelemetryData
        where TDto : AsbCloudApp.Data.ITelemetryData
    {
        private static readonly Random random = new Random((int)(DateTime.Now.Ticks % 0xFFFFFFFF));
        private readonly DbSet<TEntity> dbset;
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryService telemetryService;

        private static readonly ConcurrentDictionary<int, TDto> cache = new ();

        public WitsRecordRepository(IAsbCloudDbContext db, ITelemetryService telemetryService)
        {
            dbset = db.Set<TEntity>();
            this.db = db;
            this.telemetryService = telemetryService;
        }

        public async Task<(DateTime begin, DateTime end, int count)?> GetStatAsync(int idTelemetry, CancellationToken token)
        {
            var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours;
            var stat = await dbset.Where(d => d.IdTelemetry == idTelemetry)
                .GroupBy(d => d.IdTelemetry)
                .Select(g => new Tuple<DateTimeOffset, DateTimeOffset, int>(g.Min(d => d.DateTime), g.Max(d => d.DateTime), g.Count()))
                .FirstOrDefaultAsync(token);

            if (stat is null || stat.Item3 == 0)
                return null;

            return (
                stat.Item1.ToRemoteDateTime(timezoneHours),
                stat.Item2.ToRemoteDateTime(timezoneHours),
                stat.Item3);
        }

        public async Task<IEnumerable<TDto>> GetAsync(int idTelemetry, DateTime begin, DateTime end, CancellationToken token)
        {
            var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours;
            var query = dbset
                .Where(d => d.IdTelemetry == idTelemetry)
                .Where(d => d.DateTime >= begin)
                .Where(d => d.DateTime <= end)
                .AsNoTracking();
            var data = await query.ToListAsync(token);
            return data.Select(d => Convert(d, timezoneHours));
        }

        public TDto? GetLastOrDefault(int idTelemetry)
            => cache.GetValueOrDefault(idTelemetry);

        public async Task SaveDataAsync(int idTelemetry, IEnumerable<TDto> dtos, CancellationToken token)
        {
            if (!dtos.Any())
                return;

            cache.AddOrUpdate(idTelemetry, dtos.Last(), (_,_) => dtos.OrderBy(r => r.DateTime).Last());

            var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours;
            var entities = dtos
                .DistinctBy(d => d.DateTime)
                .Select(dto => Convert(dto, idTelemetry, timezoneHours));

            var dateMin = entities.Min(e => e.DateTime);
            var dateMax = entities.Max(e => e.DateTime);
            var existingEntities = await db.Set<AsbCloudDb.Model.WITS.RecordBase>()
                .Where(e => e.IdTelemetry == idTelemetry)
                .Where(e => e.DateTime >= dateMin && e.DateTime <= dateMax)
                .Select(e => e.DateTime)
                .OrderBy(d => d)
                .ToArrayAsync(token);
            
            foreach (var entity in entities)
            {
                if (!existingEntities.Any(e => e == entity.DateTime))
                {
                    dbset.Add(entity);
                }
                else
                {
                    var dt = entity.DateTime;
                    entity.DateTime = new DateTimeOffset(
                        dt.Year,
                        dt.Month,
                        dt.Day,
                        dt.Hour,
                        dt.Minute,
                        dt.Second,
                        (dt.Millisecond + random.Next(1, 283)) % 1000,
                        dt.Offset);
                    dbset.Add(entity);
                }                    
            }
            
            await db.SaveChangesAsync(token);
        }

        private static short GetRecId(TDto dto)
        {
            var recid = dto switch
            {
                AsbCloudApp.Data.WITS.Record1Dto _ => 1,
                AsbCloudApp.Data.WITS.Record7Dto _ => 7,
                AsbCloudApp.Data.WITS.Record8Dto _ => 8,
                AsbCloudApp.Data.WITS.Record50Dto _ => 50,
                AsbCloudApp.Data.WITS.Record60Dto _ => 60,
                AsbCloudApp.Data.WITS.Record61Dto _ => 61,
                _ => 0,
            };
            return (short)recid;
        }

        private static TEntity Convert(TDto dto, int idTelemetry, double timezoneHours)
        {
            var entity = dto.Adapt<TEntity>();
            entity.Recid = GetRecId(dto);
            entity.IdTelemetry = idTelemetry;            
            entity.DateTime = dto.DateTime.ToUtcDateTimeOffset(timezoneHours);            
            return entity;
        }

        private static TDto Convert(TEntity entity, double timezoneHours)
        {
            var data = entity.Adapt<TDto>();
            data.DateTime = entity.DateTime.ToRemoteDateTime(timezoneHours);
            return data;
        }
    }
}