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

namespace AsbCloudInfrastructure.Repository
{

    public class GtrWitsRepository : IGtrRepository
    {
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryService telemetryService;

        public GtrWitsRepository(
            IAsbCloudDbContext db,
            ITelemetryService telemetryService)
        {
            
            this.db = db;
            this.telemetryService = telemetryService;
        }

        public async Task<IEnumerable<WitsRecordDto>> GetAsync(int idWell, DateTime? dateBegin, double intervalSec = 600, int approxPointsCount = 1024, CancellationToken token = default)
        {
            var telemetry = telemetryService.GetOrDefaultTelemetryByIdWell(idWell);
            if (telemetry is null)
                return Enumerable.Empty<WitsRecordDto>();

            var timezone = telemetryService.GetTimezone(telemetry.Id);

            DateTimeOffset? dateBeginUtc = dateBegin?.ToUtcDateTimeOffset(timezone.Hours);                        
            var dateEnd = dateBeginUtc?.AddSeconds(intervalSec);

            var recordAllInt = await GetItemsOrDefaultAsync<WitsItemInt, int>(telemetry.Id, dateBeginUtc, dateEnd, approxPointsCount, timezone.Hours, token);
            var recordAllFloat = await GetItemsOrDefaultAsync<WitsItemFloat, float>(telemetry.Id, dateBeginUtc, dateEnd, approxPointsCount,timezone.Hours, token);
            var recordAllString = await GetItemsOrDefaultAsync<WitsItemString, string>(telemetry.Id, dateBeginUtc, dateEnd, approxPointsCount, timezone.Hours, token);

            var dtos = (recordAllFloat.Union(recordAllInt)).Union(recordAllString)
                .GroupBy(g => new
                {
                    g.IdRecord,
                    g.Date
                })
                .Select(g => new WitsRecordDto
                {
                    Id = g.Key.IdRecord,
                    Date = g.Key.Date,
                    Items = g.Select(r => new { 
                     Key = r.IdItem,
                     Value = r.Item
                    }).ToDictionary(x => x.Key, x => x.Value)
                });           
            return dtos;
        }

        private async Task<IEnumerable<ItemRecord>> GetItemsOrDefaultAsync<TEntity, TValue>(
            int idTelemetry,
            DateTimeOffset? dateBegin,
            DateTimeOffset? dateEnd,
            int approxPointsCount, 
            double timezoneHours,
            CancellationToken token)
            where TEntity: WitsItemBase<TValue>
            where TValue: notnull
        {
            var query = db.Set<TEntity>()
                .Where(i => i.IdTelemetry == idTelemetry);

            if (dateBegin is not null)
                query = query
                    .Where(d => d.DateTime >= dateBegin);

            if (dateEnd is not null)
                query = query
                    .Where(d => d.DateTime <= dateEnd);

            var fullDataCount = await query.CountAsync(token);

            if (fullDataCount == 0)
                return Enumerable.Empty<ItemRecord>();

            if (fullDataCount > 1.75 * approxPointsCount)
            {
                var m = (int)Math.Round(1d * fullDataCount / approxPointsCount);
                if (m > 1)
                    query = query.Where((d) => (((d.DateTime.DayOfYear * 24 + d.DateTime.Hour) * 60 + d.DateTime.Minute) * 60 + d.DateTime.Second) % m == 0);
            }

            var entities = await query
                .OrderBy(d => d.DateTime)
                .AsNoTracking()
                .ToListAsync(token)
                .ConfigureAwait(false);
            
            var items = entities.Select(e => new ItemRecord
            {
                IdRecord = e.IdRecord,
                IdTelemetry = e.IdTelemetry,
                Date = e.DateTime.ToRemoteDateTime(timezoneHours),
                IdItem = e.IdItem,
                Item = new JsonValue(e.Value)
            });
            return items;
        }

        public async Task SaveDataAsync(int idTelemetry, WitsRecordDto dto, CancellationToken token)
        {
            var timezoneHours = telemetryService.GetTimezone(idTelemetry).Hours;
            foreach (var item in dto.Items)
            {
                var dateTime = dto.Date.ToUtcDateTimeOffset(timezoneHours);
                if (item.Value.Value is string valueString)
                {
                    var entity = MakeEntity<WitsItemString, string>( dto.Id, item.Key, idTelemetry, dateTime, valueString);
                    db.WitsItemString.Add(entity);
                }
                if (item.Value.Value is float valueFloat)
                {
                    var entity = MakeEntity<WitsItemFloat, float>(dto.Id, item.Key, idTelemetry, dateTime, valueFloat);
                    db.WitsItemFloat.Add(entity);
                }
                if (item.Value.Value is int valueInt)
                {
                    var entity = MakeEntity<WitsItemInt, int>(dto.Id, item.Key, idTelemetry, dateTime, valueInt);
                    db.WitsItemInt.Add(entity);
                }                
            }            
            await db.SaveChangesAsync(token);
        }

        private static TEntity MakeEntity<TEntity, TValue>(int idRecord, int idItem, int idTelemetry, DateTimeOffset dateTime, TValue value)
            where TEntity : WitsItemBase<TValue>, new()
            where TValue: notnull
        =>  new TEntity() {
                IdRecord = idRecord,
                IdItem = idItem,
                IdTelemetry = idTelemetry,
                DateTime = dateTime,
                Value = value,
            };

        internal class ItemRecord
        {
            public int IdRecord { get; set; }            
            public int IdTelemetry { get; set; }
            public DateTime Date { get; set; }
            public int IdItem { get; set; }
            public JsonValue Item { get; set; } = default!;
        }            
    }

}