using AsbCloudApp.Requests;
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));
    }

    private IQueryable<TEntity> BuildQuery(TelemetryPartDeleteRequest request)
    {
        var query = db.Set<TEntity>()
            .Where(o => o.IdTelemetry == request.IdTelemetry);

        if (request.LeDate is not null)
        {
            var leDate = request.LeDate.Value.ToUniversalTime();
            query = query.Where(o => o.DateTime <= leDate);
        }

        if (request.GeDate is not null)
        {
            var geDate = request.GeDate.Value.ToUniversalTime();
            query = query.Where(o => o.DateTime >= geDate);
        }

        return query;
    }

    public async Task<int> DeleteAsync(TelemetryPartDeleteRequest request, CancellationToken token)
    {
        var query = BuildQuery(request);
        dbset.RemoveRange(query);
        return await db.SaveChangesAsync(token);
    }

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