using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudDb.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudInfrastructure.Services;


public class DataSaubStatService : IDataSaubStatService
{
    private IDataSaubStatRepository<DataSaubStatDto> dataSaubStatRepository;
    private ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
    private ITelemetryDataSaubService dataSaubService;
    private IDetectedOperationRepository detectedOperationRepository;

    public DataSaubStatService(
        IDataSaubStatRepository<DataSaubStatDto> dataSaubStatRepository,
        ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache,
        ITelemetryDataSaubService dataSaubService,
        IDetectedOperationRepository detectedOperationRepository)
    {
        this.dataSaubStatRepository = dataSaubStatRepository;
        this.telemetryDataCache = telemetryDataCache;
        this.dataSaubService = dataSaubService;
        this.detectedOperationRepository = detectedOperationRepository;
    }

    public async Task CreateStatAsync(int lastDaysFilter, Action<string, double?> onProgressCallback, CancellationToken token)
    {
        var cacheRequest = new TelemetryDataRequest()
        {
            GeDate = DateTime.UtcNow.AddDays(-lastDaysFilter)
        };
        var idTelemetries = telemetryDataCache.GetIds(cacheRequest).ToArray();

        if (!idTelemetries.Any())
            return;

        var stats = await dataSaubStatRepository.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, token);
            if(onProgressCallback != null)
                onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 1d * i / idTelemetries.Length);
        }
    }

    private async Task<int> CreateStatForTelemetryFromDate(
        int idTelemetry,
        DateTimeOffset begin,
        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 dataSaubStatRepository.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 + 1;

            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 + 1;
                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 aggregatedValues = CalcAggregate(span);
        var dateStart = span[0].DateTime;
        var dateEnd = span[^1].DateTime;
        var depthStart = span[0].WellDepth;
        var depthEnd = span[^1].WellDepth;
        var speed = ((depthEnd - depthStart) / (dateEnd - dateStart).TotalHours);

        var processMapDrillingCacheItem = new DataSaubStatDto
        {
            DateStart = dateStart,
            DateEnd = dateEnd,
            DepthStart = depthStart,
            DepthEnd = depthEnd,
            Speed = speed,
            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 = operation.EnabledSubsystems.IsAutoOscillation,
            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);
    }
}