using AsbCloudApp.Data;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.Cache;
using Mapster;
using System.Collections.Generic;
using System.Linq;
using System;
using Microsoft.EntityFrameworkCore;

namespace AsbCloudInfrastructure.Services
{
    public class TelemetryService : ITelemetryService
    {
        private readonly CacheTable<Telemetry> cacheTelemetry;
        private readonly CacheTable<Well> cacheWells;
        private readonly IAsbCloudDbContext db;
        private readonly ITelemetryTracker telemetryTracker;

        public TelemetryService(IAsbCloudDbContext db, ITelemetryTracker telemetryTracker, 
            CacheDb cacheDb)
        {
            cacheTelemetry = cacheDb.GetCachedTable<Telemetry>((AsbCloudDbContext)db);
            cacheWells = cacheDb.GetCachedTable<Well>((AsbCloudDbContext)db);
            this.db = db;
            this.telemetryTracker = telemetryTracker;
        }
        
        public IEnumerable<TelemetryDto> GetTransmittingTelemetriesAsync(int idCompany)
        {
            var telemetryDtos = new List<TelemetryDto>();
            IEnumerable<string> activeTelemetriesUids = telemetryTracker.GetTransmittingTelemetriesUids();
            if (activeTelemetriesUids.Any())
            {
                var telemetries = cacheTelemetry
                    .Where(t => activeTelemetriesUids.Contains(t.RemoteUid));
                telemetryDtos = telemetries.Adapt<TelemetryDto>().ToList();
            }
            
            return telemetryDtos;
        }

        public void SaveRequestDate(string uid, DateTime remoteDate) =>
            telemetryTracker.SaveRequestDate(uid, remoteDate);

        public DateTime GetLastTelemetryDate(string telemetryUid) =>
            telemetryTracker.GetLastTelemetryDateByUid(telemetryUid);

        public DateTime GetLastTelemetryDate(int telemetryId)
        {
            var lastTelemetryDate = DateTime.MinValue;
            var telemetry = cacheTelemetry.FirstOrDefault(t => t.Id == telemetryId);

            if (telemetry is null)
                return lastTelemetryDate;

            var uid = telemetry.RemoteUid;

            lastTelemetryDate = GetLastTelemetryDate(uid);
            return lastTelemetryDate;
        }

        public int GetOrCreateTemetryIdByUid(string uid)
            => GetOrCreateTelemetryByUid(uid).Id;

        public int? GetidWellByTelemetryUid(string uid)
            => GetWellByTelemetryUid(uid)?.Id;

        public double GetTimezoneOffsetByTelemetryId(int idTelemetry) =>
            cacheTelemetry.FirstOrDefault(t => t.Id == idTelemetry).Info?.TimeZoneOffsetTotalHours ?? 0d;

        public void UpdateInfo(string uid, TelemetryInfoDto info)
        {
            var telemetry = GetOrCreateTelemetryByUid(uid);
            telemetry.Info = info.Adapt<TelemetryInfo>();
            cacheTelemetry.Upsert(telemetry);
        }

        public int? GetIdTelemetryByIdWell(int idWell)
        {
            var well = cacheWells.FirstOrDefault(w => w.Id == idWell);
            if (well is null)
                return null;

            return well.IdTelemetry;
        }

        private Well GetWellByTelemetryUid(string uid)
        {
            var tele = cacheTelemetry.FirstOrDefault(t => t.RemoteUid == uid);
            if (tele is null)
                return null;

            return cacheWells.FirstOrDefault(w => w?.IdTelemetry == tele.Id);
        }

        private Telemetry GetOrCreateTelemetryByUid(string uid)
            => cacheTelemetry.GetOrCreate(t => t.RemoteUid == uid, () => new Telemetry { RemoteUid = uid });

        public IEnumerable<(string Key, int[] Ids)> GetRedundentRemoteUids()
        {
            return db.Telemetries
                .ToList()
                .GroupBy(t => t.RemoteUid)
                .Where(g => g.Count() > 1)
                .Select(g => (g.Key, g.Select(t=>t.Id).ToArray()));
        }

        public int Merge(IEnumerable<int> telemetryIds)
        {
            if (telemetryIds.Count() < 2)
                throw new ArgumentException($"telemetryIds {telemetryIds} < 2. nothing to merge.", nameof(telemetryIds));

            // найти телеметрию с наиболее полными справочниками и принять её за основную
            // отделить основную от остальных

            // Оценка трудоебкости
            var telemetriesGrade = db.Telemetries
                .Include(t => t.Messages)
                .Include(t => t.DataSaub)
                .Include(t => t.DataSpin)
                .Include(t => t.Well)
                .Where(t => telemetryIds.Contains(t.Id))
                .Select(t => new {
                    t.Id,
                    t.RemoteUid,
                    t.Info,
                    IdWell = t.Well != null ? t.Well.Id : int.MinValue,
                    Records = t.Messages.Count + t.DataSaub.Count + t.DataSpin.Count,
                    EventsAny = t.Events.Any(),
                    UsersAny = t.Users.Any(),
                })
                .OrderByDescending(t=>t.Records)
                .ToList();

            var telemetryDestId = telemetriesGrade.FirstOrDefault().Id;
            if (telemetryDestId == default)
                return 0;

            var telemetriesSrcIds = telemetryIds.Where(t => t != telemetryDestId).ToList();
            if(!telemetriesSrcIds.Any())
                return 0;

            var telemetriesSrcIdsSql = $"({string.Join(',', telemetriesSrcIds)})";

            var telemetryInfoAndUid = telemetriesGrade
                .Where(t => t.Info != null)
                .OrderByDescending(t => t.Id)
                .Select(t => (t.RemoteUid, t.Info))
                .FirstOrDefault();

            var wellId = telemetriesGrade
                .Where(t => t.IdWell > 0)
                .OrderByDescending(t => t.Id)
                .Select(t => t.IdWell)
                .FirstOrDefault();

            // начало изменений
            Console.WriteLine($"Start merge telemetries ids: [{string.Join(',', telemetriesSrcIds)}] to {telemetryDestId}");
            var sw = new System.Diagnostics.Stopwatch();
            sw.Start();
            var transaction = db.Database.BeginTransaction();
            int rows = 0;
            try
            {
                var telemetryDst = db.Telemetries.FirstOrDefault(t => t.Id == telemetryDestId);
                telemetryDst.RemoteUid = telemetryInfoAndUid.RemoteUid;
                telemetryDst.Info = telemetryInfoAndUid.Info;

                if (wellId != default)
                {
                    var well = db.Wells.FirstOrDefault(w => w.Id == wellId);
                    well.IdTelemetry = telemetryDestId;
                }

                // events merge
                var telemetryDstEventsIds = db.TelemetryEvents.Where(t => t.IdTelemetry == telemetryDestId).Select(t => t.IdEvent).ToList();
                var telemetrySrcEvents = db.TelemetryEvents
                    .Where(t => telemetriesSrcIds.Contains(t.IdTelemetry) && !telemetryDstEventsIds.Contains(t.IdEvent))
                    .Select(t => new TelemetryEvent
                    {
                        IdTelemetry = telemetryDestId,
                        IdEvent = t.IdEvent,
                        IdCategory = t.IdCategory,
                        MessageTemplate = t.MessageTemplate,
                    })
                    .ToList();
                var telemetryEventNewUniq = new Dictionary<int, TelemetryEvent>();
                foreach (var telemetryEvent in telemetrySrcEvents)
                    telemetryEventNewUniq[telemetryEvent.IdEvent] = telemetryEvent;
                if (telemetrySrcEvents.Any())
                    db.TelemetryEvents.AddRange(telemetryEventNewUniq.Values);

                // users merge
                var telemetryDstUsersIds = db.TelemetryUsers.Where(t => t.IdTelemetry == telemetryDestId).Select(t => t.IdUser).ToList();
                var telemetrySrcUsers = db.TelemetryUsers
                    .Where(t => telemetriesSrcIds.Contains(t.IdTelemetry) && !telemetryDstUsersIds.Contains(t.IdUser))
                    .Select(t => new TelemetryUser
                    {
                        IdTelemetry = telemetryDestId,
                        IdUser = t.IdUser,
                        Level = t.Level,
                        Name = t.Name,
                        Patronymic = t.Patronymic,
                        Surname = t.Surname,
                    }).ToList();
                var telemetryUserNewUniq = new Dictionary<int, TelemetryUser>();
                foreach (var telemetryUser in telemetrySrcUsers)
                    telemetryUserNewUniq[telemetryUser.IdUser] = telemetryUser;
                if (telemetrySrcUsers.Any())
                    db.TelemetryUsers.AddRange(telemetryUserNewUniq.Values);

                db.SaveChanges();

                db.Database.SetCommandTimeout(3_000); // 5 мин

                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_data_saub DISABLE TRIGGER ALL;");
                rows += db.Database.ExecuteSqlRaw($"UPDATE t_telemetry_data_saub SET id_telemetry = {telemetryDestId} WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_data_saub ENABLE TRIGGER ALL;");

                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_data_spin DISABLE TRIGGER ALL;");
                rows += db.Database.ExecuteSqlRaw($"UPDATE t_telemetry_data_spin SET id_telemetry = {telemetryDestId} WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_data_spin ENABLE TRIGGER ALL;");

                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_analysis DISABLE TRIGGER ALL;");
                rows += db.Database.ExecuteSqlRaw($"UPDATE t_telemetry_analysis SET id_telemetry = {telemetryDestId} WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_analysis ENABLE TRIGGER ALL;");

                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_message DISABLE TRIGGER ALL;");
                rows += db.Database.ExecuteSqlRaw($"UPDATE t_telemetry_message SET id_telemetry = {telemetryDestId} WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                db.Database.ExecuteSqlRaw($"ALTER TABLE t_telemetry_message ENABLE TRIGGER ALL;");

                rows += db.Database.ExecuteSqlRaw($"DELETE FROM t_telemetry_event WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                rows += db.Database.ExecuteSqlRaw($"DELETE FROM t_telemetry_user WHERE id_telemetry IN {telemetriesSrcIdsSql};");
                rows += db.Database.ExecuteSqlRaw($"DELETE FROM t_telemetry WHERE id IN {telemetriesSrcIdsSql};");

                transaction.Commit();
                sw.Stop();
                Console.WriteLine($"Successfully commited in {1d*sw.ElapsedMilliseconds/1000d: #0.00} sec. Affected {rows} rows.");
            }
            catch(Exception ex)
            {
                Console.WriteLine($"Fail. Rollback. Reason is:{ex.Message}");
                transaction.Rollback();
                return 0;
            }
            
            return rows;
        }
    }
}