using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services.SAUB
{
    public class TelemetryTracker : ITelemetryTracker
    {
        class TrackerStat
        {
            //public int Id { get; set; }

            public string RemoteUid { get; set; }

            /// <summary>
            /// Время последнего запроса (по времени сервера)
            /// </summary>
            public DateTimeOffset LastTimeServer { get; set; }

            /// <summary>
            /// Дата первых данных в БД
            /// </summary>
            public DateTimeOffset TelemetryDateUtcMin { get; set; }

            /// <summary>
            /// Дата последних данных в БД
            /// </summary>
            public DateTimeOffset TelemetryDateUtcMax { get; set; }

        }

        private readonly ConcurrentDictionary<string, TrackerStat> telemetriesStats;

        public TelemetryTracker(IConfiguration configuration, IMemoryCache memoryCache)
        {
            // TODO: make this background work
            var contextOptions = new DbContextOptionsBuilder<AsbCloudDbContext>()
                .UseNpgsql(configuration.GetConnectionString("DefaultConnection"))
                .Options;
            var db = new AsbCloudDbContext(contextOptions);

            var cacheTelemetry = memoryCache.GetOrCreateBasic(db.Set<Telemetry>().Include(t=>t.Well));
            var keyValuePairs = new Dictionary<string, TrackerStat>(cacheTelemetry.Count());
            foreach (var telemetry in cacheTelemetry)
            {
                var date = telemetry.Info?.DrillingStartDate
                    ?? ParseDateFromUidOrDefault(telemetry.RemoteUid, DateTimeOffset.MinValue);

                keyValuePairs[telemetry.RemoteUid] = new TrackerStat
                {
                    RemoteUid = telemetry.RemoteUid,
                    TelemetryDateUtcMin = date,
                    TelemetryDateUtcMax = date,
                    LastTimeServer = date,
                };
            }
            telemetriesStats = new ConcurrentDictionary<string, TrackerStat>(keyValuePairs);

            Task.Run(async () =>
            {
                db.Database.SetCommandTimeout(2 * 60);
                var dates = await db.TelemetryDataSaub
                    .GroupBy(d => d.IdTelemetry)
                    .Select(g => new
                    {
                        IdTelemetry = g.Key,
                        DateMax = g.Max(d => d.DateTime),
                        DateMin = g.Min(d => d.DateTime),
                    })
                    .AsNoTracking()
                    .ToListAsync()
                    .ConfigureAwait(false);

                var oldRequests = dates.Select(t => new
                {
                    Uid = cacheTelemetry.FirstOrDefault(c => c.Id == t.IdTelemetry)?.RemoteUid,
                    t.DateMax,
                    t.DateMin,
                }).Where(s => !string.IsNullOrEmpty(s.Uid));

                foreach (var oldReq in oldRequests)
                {
                    var telemetryStat = telemetriesStats.GetOrAdd(oldReq.Uid, (uid) => new TrackerStat { RemoteUid = uid });
                    telemetryStat.TelemetryDateUtcMin = oldReq.DateMin;
                    telemetryStat.TelemetryDateUtcMax = oldReq.DateMax;
                    telemetryStat.LastTimeServer = oldReq.DateMax;
                }
            }).ContinueWith((t) =>
            {
                db.Dispose();
                return t;
            });
        }

        private static DateTimeOffset ParseDateFromUidOrDefault(string remoteUid, DateTimeOffset defaultValue = default)
        {
            //example: uid = 20211102_173407926
            if (string.IsNullOrEmpty(remoteUid) || remoteUid.Length != 18)
                return defaultValue;

            if (DateTime.TryParseExact(remoteUid, "yyyyMMdd_HHmmssfff",
                                   System.Globalization.CultureInfo.InvariantCulture,
                                   System.Globalization.DateTimeStyles.AssumeUniversal,
                                   out DateTime parsedDate))
                return parsedDate;

            return defaultValue;
        }

        public void SaveRequestDate(string uid, DateTimeOffset remoteDate)
        {
            var stat = telemetriesStats.GetOrAdd(uid, _ => new TrackerStat
            {
                RemoteUid = uid,
                TelemetryDateUtcMin = remoteDate
            }
            );

            stat.LastTimeServer = DateTime.Now;

            if (stat.TelemetryDateUtcMax.ToUniversalTime() < remoteDate.ToUniversalTime())
                stat.TelemetryDateUtcMax = remoteDate;
        }

        public DateTimeOffset GetLastTelemetryDateByUid(string uid) =>
            telemetriesStats.GetValueOrDefault(uid)?.TelemetryDateUtcMax ?? default;

        public DatesRangeDto GetTelemetryDateRangeByUid(string uid)
        {
            var stat = telemetriesStats.GetValueOrDefault(uid);
            var range = new DatesRangeDto
            {
                From = stat?.TelemetryDateUtcMin.UtcDateTime ?? default,
                To = stat?.TelemetryDateUtcMax.UtcDateTime ?? default,
            };
            return range;
        }

        public IEnumerable<string> GetTransmittingTelemetriesUids() =>
            telemetriesStats.Keys;
    }
}