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

namespace AsbCloudInfrastructure.Services;


public class DataSaubStatDrillingQualityService : IDataSaubStatDrillingQualityService
{
    private IDataSaubStatRepository<DataSaubStatDrillingQualityDto> dataSaubStatDrillingQualityRepository;
    private ITelemetryDataSaubService dataSaubService;
    private ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache;
    private ITelemetryService telemetryService;

    private static int IdFeedRegulatorRop = 1;
    private static int IdFeedRegulatorPressureDelta = 2;
    private static int IdFeedRegulatorAxialLoad = 3;
    private static int IdFeedRegulatorTorque = 4;

    public Dictionary<int, Predicate<TelemetryDataSaubDto>> QualitySettingsForFeedRegulators { get; }

    public DataSaubStatDrillingQualityService(
        IDataSaubStatRepository<DataSaubStatDrillingQualityDto> dataSaubStatDrillingQualityRepository,
        ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCache,
        ITelemetryDataSaubService dataSaubService,
        ITelemetryService telemetryService)
    {
        this.dataSaubStatDrillingQualityRepository = dataSaubStatDrillingQualityRepository;
        this.dataSaubService = dataSaubService;
        this.telemetryDataCache = telemetryDataCache;
        this.telemetryService = telemetryService;

        Predicate<TelemetryDataSaubDto> hasQualityWithIdFeedRegulator1 = (TelemetryDataSaubDto spanItem)
           => (spanItem.BlockSpeed >= spanItem.BlockSpeedSp * 0.95
                && spanItem.BlockSpeed <= spanItem.BlockSpeedSp * 1.05);

        Predicate<TelemetryDataSaubDto> hasQualityWithIdFeedRegulator2 = (TelemetryDataSaubDto spanItem)
           => (spanItem.Pressure >= (spanItem.PressureSp) - 5
                && spanItem.Pressure <= (spanItem.PressureSp) + 5);

        Predicate<TelemetryDataSaubDto> hasQualityWithIdFeedRegulator3 = (TelemetryDataSaubDto spanItem)
           => (spanItem.AxialLoad >= (spanItem.AxialLoadSp - 0.5)
                && spanItem.AxialLoad <= (spanItem.AxialLoadSp + 0.5));

        Predicate<TelemetryDataSaubDto> hasQualityWithIdFeedRegulator4 = (TelemetryDataSaubDto spanItem)
          => (spanItem.RotorTorque >= (spanItem.RotorTorqueSp - 0.5)
                && spanItem.RotorTorque <= (spanItem.RotorTorqueSp + 0.5));

        QualitySettingsForFeedRegulators = new Dictionary<int, Predicate<TelemetryDataSaubDto>>() {
            { IdFeedRegulatorRop, hasQualityWithIdFeedRegulator1 },
            { IdFeedRegulatorPressureDelta, hasQualityWithIdFeedRegulator2 },
            { IdFeedRegulatorAxialLoad, hasQualityWithIdFeedRegulator3 },
            { IdFeedRegulatorTorque, hasQualityWithIdFeedRegulator4 },
        };
    }

    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 dataSaubStatDrillingQualityRepository.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 result = await CreateStatDrillingQualityForTelemetry(idTelemetry, lastDate, token);

            var statsCount = result.Count();
            if (result.Any())
                statsCount = await dataSaubStatDrillingQualityRepository.InsertRangeAsync(result, token);

            if (onProgressCallback != null)
                onProgressCallback($"Calculate stat for telemetry: {idTelemetry}; from {lastDate}; results count: {statsCount};", 1d * i / idTelemetries.Length);
        }
    }

    public async Task<IEnumerable<DataSaubStatDrillingQualityDto>> CreateStatDrillingQualityForTelemetry(
        int idTelemetry,
        DateTimeOffset geDate,
        CancellationToken token)
    {
        var leDate = DateTimeOffset.UtcNow;
        var result = new List<DataSaubStatDrillingQualityDto>();

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

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

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

        foreach (var item in QualitySettingsForFeedRegulators)
        {
            var data = CreateDataSaubStatDrillingQuality(item.Key, item.Value, dataSaubArray);
            result.AddRange(data);
        }

        return result;
    }

    private static IEnumerable<DataSaubStatDrillingQualityDto> CreateDataSaubStatDrillingQuality(
        int idFeedRegulator,
        Predicate<TelemetryDataSaubDto> checkQuality,
        TelemetryDataSaubDto[] dataSaub)
    {
        var result = new List<DataSaubStatDrillingQualityDto>();

        var indexStart = 0;
        var indexEnd = 0;

        while (indexEnd < dataSaub.Count() - 1)
        {
            indexStart = Array.FindIndex(dataSaub, indexEnd, t => t.IdFeedRegulator == idFeedRegulator);
            if (indexStart < 0)
                break;

            indexEnd = FindIndexEnd(indexStart, idFeedRegulator, dataSaub);

            var length = indexEnd - indexStart + 1;
            var subset = dataSaub.AsSpan(indexStart, length);

            if ((subset[^1].WellDepth - subset[0].WellDepth) < 0.15)
                continue; // мелкие выборки не учитываем.

            var stats = CalcStatsDrillingQuality(idFeedRegulator, subset, checkQuality);
            result.Add(stats);

        }
        return result;
    }

    private static int FindIndexEnd(int indexStart, int idFeedRegulator, TelemetryDataSaubDto[] dataSaub)
    {
        var indexEnd = indexStart + 1;
        for (var i = indexStart + 1; i < dataSaub.Count(); i++)
        {
            if (dataSaub[i].WellDepth >= dataSaub[i - 1].WellDepth)
            {
                indexEnd = i;
            }
            if (dataSaub[i].IdFeedRegulator != idFeedRegulator)
                break;
        }
        return indexEnd;
    }

    private static DataSaubStatDrillingQualityDto CalcStatsDrillingQuality(
        int idFeedRegulator,
        Span<TelemetryDataSaubDto> dataSaub,
        Predicate<TelemetryDataSaubDto> predicate
        )
    {
        var result = new DataSaubStatDrillingQualityDto();
        result.IdTelemetry = dataSaub[0].IdTelemetry;
        result.IdFeedRegulator = idFeedRegulator;
        result.DepthStart = dataSaub[0].WellDepth;
        result.DepthEnd = dataSaub[^1].WellDepth;
        result.DateStart = dataSaub[0].DateTime;
        result.DateEnd = dataSaub[^1].DateTime;

        var depthDrillingQuality = 0.0;
        for (var i = 0; i < dataSaub.Length - 1; i++)
        {
            if (predicate(dataSaub[i]))
            {
                depthDrillingQuality += dataSaub[i + 1].WellDepth - dataSaub[i].WellDepth;
            }
        }
        result.DepthDrillingQuality = depthDrillingQuality;

        return result;
    }

    public async Task<IEnumerable<DrillingQualityDto>> GetStatAsync(DrillingQualityRequest request, CancellationToken token)
    {
        var telemetries = telemetryService.GetOrDefaultTelemetriesByIdsWells(request.IdsWell);
        var idsTelemetriesWithIdWell = telemetries
            .Where(t => t.IdWell.HasValue)
            .ToDictionary(t => t.Id, t => t.IdWell!.Value);

        var dataSaubStatDrillingQuality = await dataSaubStatDrillingQualityRepository.GetAsync(idsTelemetriesWithIdWell.Keys, request.GeDate, request.LeDate, token);

        var dtosGroupedByIdTelemetries = dataSaubStatDrillingQuality
            .GroupBy(s => new { s.IdTelemetry, s.IdFeedRegulator })
            .Select(stat => new
            {
                IdFeedRegulator = stat.Key.IdFeedRegulator,
                IdTelemetry = stat.Key.IdTelemetry,
                Kpd = (stat.Sum(s => s.DepthDrillingQuality) / stat.Sum(s => s.DepthEnd - s.DepthStart)) * 100,
            })
            .GroupBy(stat => stat.IdTelemetry);

        var result = new List<DrillingQualityDto>();
        foreach (var dtos in dtosGroupedByIdTelemetries)
        {
            var kpdValuesByIdFeedRegulator = dtos
                .GroupBy(d => d.IdFeedRegulator)
                .ToDictionary(d => d.Key, d => d.FirstOrDefault()!.Kpd);

            var item = new DrillingQualityDto()
            {
                IdWell = idsTelemetriesWithIdWell[dtos.Key],
                KpdAxialLoad = kpdValuesByIdFeedRegulator.GetValueOrDefault(IdFeedRegulatorAxialLoad),
                KpdPressureDelta = kpdValuesByIdFeedRegulator.GetValueOrDefault(IdFeedRegulatorPressureDelta),
                KpdRop = kpdValuesByIdFeedRegulator.GetValueOrDefault(IdFeedRegulatorRop),
                KpdTorque = kpdValuesByIdFeedRegulator.GetValueOrDefault(IdFeedRegulatorTorque)
            };

            result.Add(item);
        }

        return result;
    }
}