using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Data.ProcessMaps.Report;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps.WellDrilling;
using AsbCloudInfrastructure.Services.ProcessMaps.Report.Data;

namespace AsbCloudInfrastructure.Services.ProcessMaps.Report;

public class ProcessMapReportWellDrillingService : IProcessMapReportWellDrillingService
{
    private readonly IWellService wellService;
    private readonly IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillingRepository;
    private readonly ITelemetryDataSaubService telemetryDataSaubService;
    private readonly IWellOperationRepository wellOperationRepository;

    public ProcessMapReportWellDrillingService(IWellService wellService,
       IProcessMapPlanRepository<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillingRepository,
        ITelemetryDataSaubService telemetryDataSaubService,
        IWellOperationRepository wellOperationRepository)
    {
        this.wellService = wellService;
        this.processMapPlanWellDrillingRepository = processMapPlanWellDrillingRepository;
        this.telemetryDataSaubService = telemetryDataSaubService;
        this.wellOperationRepository = wellOperationRepository;
    }

    public async Task<IEnumerable<ProcessMapReportWellDrillingDto>> GetAsync(int idWell,
        CancellationToken token)
    {
        var well = await wellService.GetOrDefaultAsync(idWell, token)
                   ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважина с Id: {idWell} не найдена");

        if (!well.IdTelemetry.HasValue)
            return Enumerable.Empty<ProcessMapReportWellDrillingDto>();

        var processMapPlanWellDrillings = await processMapPlanWellDrillingRepository.GetByIdWellAsync(idWell, token);

        if (!processMapPlanWellDrillings.Any())
            return Enumerable.Empty<ProcessMapReportWellDrillingDto>();

        var telemetryDataStat =
            (await telemetryDataSaubService.GetTelemetryDataStatAsync(well.IdTelemetry.Value, token)).ToArray();

        if (!telemetryDataStat.Any())
            return Enumerable.Empty<ProcessMapReportWellDrillingDto>();

        var result = CalcByIntervals(processMapPlanWellDrillings, telemetryDataStat);

        return result;
    }

    private IEnumerable<ProcessMapReportWellDrillingDto> CalcByIntervals(
        IEnumerable<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillings,
        TelemetryDataSaubStatDto[] telemetryDataStat)
    {
        var processMapIntervals = CalcDepthIntervals(processMapPlanWellDrillings);

        var result = new List<ProcessMapReportWellDrillingDto>(processMapIntervals.Count() * 4);

        var telemetryIndexStart =
            Array.FindIndex(telemetryDataStat, t => t.WellDepthMin >= processMapIntervals.First().DepthStart);
        if (telemetryIndexStart < 0)
            return Enumerable.Empty<ProcessMapReportWellDrillingDto>();

        IDictionary<int, string> sectionTypes = wellOperationRepository
            .GetSectionTypes()
            .ToDictionary(s => s.Id, s => s.Caption);

        foreach (var interval in processMapIntervals)
        {
            var processMapPlanWellDrillingInterval = processMapPlanWellDrillings
                .Where(p => p.DepthStart <= interval.DepthEnd && p.DepthEnd >= interval.DepthStart);

            if (!processMapPlanWellDrillingInterval.Any())
                continue;

            var telemetryIndexEnd = Array.FindIndex(telemetryDataStat, telemetryIndexStart,
                t => t.WellDepthMin >= interval.DepthEnd);
            if (telemetryIndexEnd < 0)
                telemetryIndexEnd = telemetryDataStat.Length - 1;
            var telemetryDataInterval =
                telemetryDataStat.AsSpan(telemetryIndexStart, telemetryIndexEnd - telemetryIndexStart);

            IEnumerable<ProcessMapReportWellDrillingDto> subIntervalsResult =
                CalcSubIntervals(interval, processMapPlanWellDrillingInterval, telemetryDataInterval, sectionTypes);

            result.AddRange(subIntervalsResult);
            telemetryIndexStart = telemetryIndexEnd;
        }

        return result;
    }

    private static IEnumerable<(double DepthStart, double DepthEnd)> CalcDepthIntervals(
        IEnumerable<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillings)
    {
        if (!processMapPlanWellDrillings.Any())
            yield break;

        var intervalStarts = processMapPlanWellDrillings
            .OrderBy(i => i.DepthStart)
            .Select(p => p.DepthStart)
            .Distinct()
            .ToArray();

        for (var i = 1; i < intervalStarts.Length; i++)
            yield return (intervalStarts[i - 1], intervalStarts[i]);

        yield return (intervalStarts[^1], processMapPlanWellDrillings.Max(p => p.DepthEnd));
    }

    private static IEnumerable<ProcessMapReportWellDrillingDto> CalcSubIntervals(
        (double DepthStart, double DepthEnd) interval,
        IEnumerable<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillingInterval,
        Span<TelemetryDataSaubStatDto> telemetryDataInterval,
        IDictionary<int, string> sectionTypes)
    {
        var telemetryDataIntervalLength = telemetryDataInterval.Length;
        if (telemetryDataInterval.Length == 0)
            return Enumerable.Empty<ProcessMapReportWellDrillingDto>();

        var result = new List<ProcessMapReportWellDrillingDto>();
        var telemetryIndexStart = 0;
        var subInterval = interval;

        for (var i = telemetryIndexStart + 1; i < telemetryDataIntervalLength; i++)
        {
            if (IsDifferent(telemetryDataInterval[telemetryIndexStart], telemetryDataInterval[i]))
            {
                subInterval.DepthEnd = telemetryDataInterval[i - 1].WellDepthMax;
                var telemetryRowSpan = telemetryDataInterval[telemetryIndexStart..(i - 1)];

                if (!telemetryRowSpan.IsEmpty)
                {
                    var intervalReportRow = CalcSubIntervalReportRow(subInterval, processMapPlanWellDrillingInterval,
                        telemetryRowSpan, sectionTypes);
                    result.Add(intervalReportRow);
                }

                telemetryIndexStart = i;
                subInterval.DepthStart = subInterval.DepthEnd;
            }
        }

        subInterval.DepthEnd = interval.DepthEnd;
        var intervalReportRowLast = CalcSubIntervalReportRow(subInterval, processMapPlanWellDrillingInterval,
            telemetryDataInterval[telemetryIndexStart..telemetryDataIntervalLength], sectionTypes);
        result.Add(intervalReportRowLast);
        return result;
    }

    private static ProcessMapReportWellDrillingDto CalcSubIntervalReportRow(
        (double DepthStart, double DepthEnd) subInterval,
        IEnumerable<ProcessMapPlanWellDrillingDto> processMapPlanWellDrillings,
        Span<TelemetryDataSaubStatDto> telemetryRowSpan,
        IDictionary<int, string> sectionTypes)
    {
        var telemetryStat = new TelemetryStat(telemetryRowSpan);
        var processMapByMode = processMapPlanWellDrillings.FirstOrDefault(p => p.IdMode == telemetryStat.IdMode);
        var processMapFirst = processMapPlanWellDrillings.First();
        var idWellSectionType = processMapByMode?.IdWellSectionType ?? processMapFirst.IdWellSectionType;

        var result = new ProcessMapReportWellDrillingDto
        {
            IdWell = processMapByMode?.IdWell ?? processMapFirst.IdWell,
            IdWellSectionType = idWellSectionType,
            WellSectionTypeName = sectionTypes[idWellSectionType],

            DepthStart = subInterval.DepthStart,
            DepthEnd = subInterval.DepthEnd,
            DateStart = telemetryStat.DateStart,

            MechDrillingHours = telemetryStat.DrillingHours,
            DrillingMode = telemetryStat.ModeName,

            DeltaDepth = telemetryStat.DeltaDepth,

            PressureDiff = telemetryStat.Pressure.MakeParams(processMapByMode?.Pressure.Plan),
            AxialLoad = telemetryStat.AxialLoad.MakeParams(processMapByMode?.AxialLoad.Plan),
            TopDriveTorque = telemetryStat.RotorTorque.MakeParams(processMapByMode?.TopDriveTorque.Plan),
            SpeedLimit = telemetryStat.BlockSpeed.MakeParams(processMapByMode?.RopPlan),

            Rop = new PlanFactDto<double?>
            {
                Plan = processMapByMode?.RopPlan,
                Fact = telemetryStat.Rop
            },

            UsagePlan = processMapByMode?.UsageSaub ?? telemetryStat.UsagePredictPlan,
            UsageFact = telemetryStat.UsageSaub,
        };
        return result;
    }

    private static bool IsDifferent(TelemetryDataSaubStatDto intervalStart, TelemetryDataSaubStatDto current)
    {
        if (intervalStart.WellDepthMin > current.WellDepthMin)
            return true;

        if (intervalStart.IdMode != current.IdMode)
            return true;

        if (Math.Abs(intervalStart.PressureSp - current.PressureSp) > 5d)
            return true;

        if (Math.Abs(intervalStart.AxialLoadSp - current.AxialLoadSp) > 1d)
            return true;

        if (Math.Abs(intervalStart.RotorTorqueSp - current.RotorTorqueSp) > 5d)
            return true;

        var blockSpeedSpDiff = Math.Abs(intervalStart.BlockSpeedSp - current.BlockSpeedSp);
        if (!(blockSpeedSpDiff > 5d))
            return false;

        switch (intervalStart.BlockSpeedSp)
        {
            case <= 30:
            case > 30 when blockSpeedSpDiff > 15d:
            case > 80 when blockSpeedSpDiff > 20d:
                return true;
        }

        return false;
    }
}