using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.DetectOperations;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Background.PeriodicWorks
{
    /// <summary>
    /// задача по добавлению данных в таблицу DataSaubStat, которая используется дл построения РТК-отчета
    /// </summary>
    internal class WorkDataSaubStat : Work
    {
        private int Gap = 60;

        public WorkDataSaubStat() : base("Generate DataSaubStat entries and save them into Db")
        {
            Timeout = TimeSpan.FromMinutes(10);
        }

        protected override async Task Action(string id, IServiceProvider services, Action<string, double?> onProgressCallback, CancellationToken token)
        {

            var telemetryDataCache = services.GetRequiredService<ITelemetryDataCache<TelemetryDataSaubDto>>();

            var cacheRequest = new TelemetryDataRequest()
            {
                GeDate = DateTime.UtcNow.AddDays(-Gap)
            };
            var idTelemetries = telemetryDataCache.GetIds(cacheRequest).ToArray();

            if (!idTelemetries.Any())
                return;

            var dataSaubStatRepo = services.GetRequiredService<IDataSaubStatRepository>();
            var dataSaubService = services.GetRequiredService<ITelemetryDataSaubService>();
            var detectedOperationRepository = services.GetRequiredService<IDetectedOperationRepository>();
            var stats = await dataSaubStatRepo.GetLastsAsync(idTelemetries, token);

            for( var i =0; i < idTelemetries.Length; i++)
            {
                var idTelemetry = idTelemetries[i];
                var lastDate = stats.FirstOrDefault(s => s.IdTelemetry == idTelemetry)?.DateEnd.ToUniversalTime() ?? DateTimeOffset.UnixEpoch;
                var statsCount = await CreateStatForTelemetryFromDate(idTelemetry, lastDate, dataSaubService, dataSaubStatRepo, detectedOperationRepository, token);
                onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 100*i / idTelemetries.Length);
            }
        }

        private static async Task<int> CreateStatForTelemetryFromDate(
            int idTelemetry, 
            DateTimeOffset begin, 
            ITelemetryDataSaubService dataSaubService, 
            IDataSaubStatRepository dataSaubStatRepo,
            IDetectedOperationRepository detectedOperationRepository,
            CancellationToken token)
        {
            var detectedOperationRequest = new DetectedOperationByTelemetryRequest { 
                GeDateStart = begin,
                IdTelemetry = idTelemetry,
                IdsCategories = WellOperationCategory.MechanicalDrillingSubIds,
                SortFields = new[] {nameof(DetectedOperation.DateStart) },
                Take = 250,
            };

            var detectedOperations = await detectedOperationRepository.Get(detectedOperationRequest, token);

            if (!detectedOperations.Any())
                return 0;

            var geDate = detectedOperations.First().DateStart;
            var leDate = detectedOperations.OrderByDescending(d => d.DateEnd).First().DateEnd;

            var dataSaub = await dataSaubService.Get(idTelemetry, true, geDate, leDate, 100_000, token);

            if (!dataSaub.Any())
                return 0;

            if(dataSaub is not TelemetryDataSaubDto[] dataSaubArray)
                dataSaubArray = dataSaub.ToArray();

            var dataSaubStats = CreateDataSaubStat(detectedOperations, dataSaubArray);

            return await dataSaubStatRepo.InsertRangeAsync(dataSaubStats, token);
        }

        private static IEnumerable<DataSaubStatDto> CreateDataSaubStat(IEnumerable<DetectedOperationDto> detectedOperations, TelemetryDataSaubDto[] dataSaub)
        {
            var indexStart = 0;
            var indexEnd = 0;
            var result = new List<DataSaubStatDto>();

            if (!dataSaub.Any())
                return result;

            foreach (var operation in detectedOperations)
            {
                indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.DateTime >= operation.DateStart);
                if (indexStart < 0)
                    break;

                indexEnd = Array.FindIndex(dataSaub, indexStart, t => t.DateTime > operation.DateEnd);

                if (indexEnd < 0)
                    indexEnd = dataSaub.Length - 1;

                if (indexEnd == indexStart)
                    continue;

                var length = indexEnd - indexStart;

                var subset = dataSaub.AsSpan(indexStart, length);
                var stats = CalcStats(operation, subset);
                result.AddRange(stats);
            }
            return result;
        }

        private static IEnumerable<DataSaubStatDto> CalcStats(DetectedOperationDto operation, Span<TelemetryDataSaubDto> dataSaub)
        {
            var result = new List<DataSaubStatDto>();

            var indexStart = 0;
            for (var i = 1; i < dataSaub.Length; i++)
            {
                var previous = dataSaub[i - 1];
                var current = dataSaub[i];

                if (IsNewCacheItem(previous, current) || i == dataSaub.Length - 1)
                {
                    var length = i - indexStart;
                    var span = dataSaub.Slice(indexStart, length);
                    indexStart = i;
                    if (length <= 2 || (span[^1].WellDepth - span[0].WellDepth) < 0.001)
                        continue; // мелкие выборки не учитываем.
                    var stat = CalcStat(operation, span);
                    result.Add(stat);
                }
            }
            
            return result;
        }

        private static DataSaubStatDto CalcStat(DetectedOperationDto operation, Span<TelemetryDataSaubDto> span)
        {
            var hasOscillation = EnabledSubsystemsFlags.AutoOscillation.HasEnabledSubsystems(operation.EnabledSubsystems);

            var aggregatedValues = CalcAggregate(span);
            var processMapDrillingCacheItem = new DataSaubStatDto
            {
                DateStart = operation.DateStart,
                DateEnd = operation.DateEnd,
                DepthStart = operation.DepthStart,
                DepthEnd = operation.DepthEnd,
                Speed = (operation.DepthEnd - operation.DepthStart) / ((operation.DateEnd - operation.DateStart).TotalHours),
                BlockSpeedSp = span[0].BlockSpeedSp,
                Pressure = aggregatedValues.Pressure,
                PressureIdle = span[0].PressureIdle,
                PressureSp = span[0].PressureSp,
                AxialLoad = aggregatedValues.AxialLoad,
                AxialLoadSp = span[0].AxialLoadSp,
                AxialLoadLimitMax = span[0].AxialLoadLimitMax,
                RotorTorque = aggregatedValues.RotorTorque,
                RotorTorqueSp = span[0].RotorTorqueSp,
                RotorTorqueLimitMax = span[0].RotorTorqueLimitMax,
                IdFeedRegulator = span[0].IdFeedRegulator,
                RotorSpeed = aggregatedValues.RotorSpeed,
                IdCategory = operation.IdCategory,
                EnabledSubsystems = operation.EnabledSubsystems,
                HasOscillation = hasOscillation,
                IdTelemetry = operation.IdTelemetry,
                Flow = aggregatedValues.Flow
            };
            return processMapDrillingCacheItem;
        }

        private static (
            double Pressure,
            double AxialLoad,
            double RotorTorque,
            double RotorSpeed,
            double Flow
            ) CalcAggregate(Span<TelemetryDataSaubDto> span)
        {
            var sumPressure = 0.0;
            var sumAxialLoad = 0.0;
            var sumRotorTorque = 0.0;
            var sumRotorSpeed = 0.0;
            var flow = span[0].Flow ?? 0.0;
            var diffDepthTotal = span[^1].WellDepth - span[0].WellDepth;
            for (var i = 0; i < span.Length - 1; i++)
            {
                var diffDepth = span[i + 1].WellDepth - span[i].WellDepth;
                sumPressure += diffDepth * span[i].Pressure;
                sumAxialLoad += diffDepth * span[i].AxialLoad;
                sumRotorTorque += diffDepth * span[i].RotorTorque;
                sumRotorSpeed += diffDepth * span[i].RotorSpeed;
                flow = span[i + 1].Flow > flow ? span[i + 1].Flow ?? 0.0 : flow;
            }
            return (
                Pressure: sumPressure / diffDepthTotal,
                AxialLoad: sumAxialLoad / diffDepthTotal,
                RotorTorque: sumRotorTorque / diffDepthTotal,
                RotorSpeed: sumRotorSpeed / diffDepthTotal,
                Flow: flow
            );
        }

        private static bool IsNewCacheItem(TelemetryDataSaubDto previous, TelemetryDataSaubDto current)
        {
            return !(current.Mode == previous.Mode)
                 || !(current.WellDepth >= previous.WellDepth)
                 || !(current.BlockSpeedSp == previous.BlockSpeedSp)
                 || !(current.PressureIdle == previous.PressureIdle)
                 || !(current.PressureSp == previous.PressureSp)
                 || !(current.AxialLoadSp == previous.AxialLoadSp)
                 || !(current.AxialLoadLimitMax == previous.AxialLoadLimitMax)
                 || !(current.HookWeightIdle == previous.HookWeightIdle)
                 || !(current.RotorTorqueIdle == previous.RotorTorqueIdle)
                 || !(current.RotorTorqueSp == previous.RotorTorqueSp)
                 || !(current.RotorTorqueLimitMax == previous.RotorTorqueLimitMax)
                 || !(current.IdFeedRegulator == previous.IdFeedRegulator);
        }
    }
}