using AsbCloudApp.Data;
using AsbCloudApp.Data.DetectedOperation;
using AsbCloudApp.Data.SAUB;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudInfrastructure.Services;
using Mapster;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Xunit;

namespace AsbCloudInfrastructure.Tests.Services;

public class DataSaubStatServiceTest
{
    private readonly int Gap = 5;
    private readonly IDataSaubStatRepository dataSaubStatRepositoryMock = Substitute.For<IDataSaubStatRepository>();
    private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCacheMock = Substitute.For<ITelemetryDataCache<TelemetryDataSaubDto>>();
    private readonly IDetectedOperationRepository detectedOperationRepositoryMock = Substitute.For<IDetectedOperationRepository>();
    private readonly ITelemetryDataSaubService dataSaubServiceMock = Substitute.For<ITelemetryDataSaubService>();

    private DataSaubStatService dataSaubStatService;

    private int[] idTelemetries = [1];
    private IEnumerable<DataSaubStatDto> dataSaubStatDtos = new List<DataSaubStatDto>()
    {
        new DataSaubStatDto {
            Id = 1,
            AxialLoad = 1,
            AxialLoadLimitMax = 1,
            AxialLoadSp = 1,
            BlockSpeedSp = 1,
            DateEnd = DateTime.UtcNow,
            DateStart = DateTime.UtcNow.AddHours(-1),
            DepthEnd = 2,
            DepthStart = 1,
            EnabledSubsystems = 1,
            Flow = 1,
            HasOscillation = true,
            IdCategory = 1,
            IdFeedRegulator = 1,
            IdTelemetry = 1,
            Pressure = 1,
            PressureIdle = 1,
            PressureSp = 1,
            RotorSpeed = 1,
            RotorTorque = 1,
            RotorTorqueLimitMax = 1,
            RotorTorqueSp = 1,
            Speed = 1
        },
        new DataSaubStatDto {
            Id = 2,
            AxialLoad = 2,
            AxialLoadLimitMax = 2,
            AxialLoadSp = 2,
            BlockSpeedSp = 2,
            DateEnd = DateTime.UtcNow,
            DateStart = DateTime.UtcNow.AddHours(-1),
            DepthEnd = 3,
            DepthStart = 2,
            EnabledSubsystems = 2,
            Flow = 2,
            HasOscillation = true,
            IdCategory = 2,
            IdFeedRegulator = 2,
            IdTelemetry = 2,
            Pressure = 2,
            PressureIdle = 2,
            PressureSp = 2,
            RotorSpeed = 2,
            RotorTorque = 2,
            RotorTorqueLimitMax = 2,
            RotorTorqueSp = 2,
            Speed = 2
        },
        new DataSaubStatDto {
            Id = 3,
            AxialLoad = 3,
            AxialLoadLimitMax = 3,
            AxialLoadSp = 3,
            BlockSpeedSp = 3,
            DateEnd = DateTime.UtcNow,
            DateStart = DateTime.UtcNow.AddHours(-1),
            DepthEnd = 4,
            DepthStart = 3,
            EnabledSubsystems = 3,
            Flow = 3,
            HasOscillation = true,
            IdCategory = 3,
            IdFeedRegulator = 3,
            IdTelemetry = 3,
            Pressure = 3,
            PressureIdle = 3,
            PressureSp = 3,
            RotorSpeed = 3,
            RotorTorque = 3,
            RotorTorqueLimitMax = 3,
            RotorTorqueSp = 3,
            Speed = 3
        },
    };

    private List<DetectedOperationDto> detectedOperationDtos = new List<DetectedOperationDto>() {
        new DetectedOperationDto {
            Id = 1,
            DateEnd = DateTimeOffset.UtcNow,
            DateStart = DateTimeOffset.UtcNow.AddHours(-1),
            DepthStart = 1,
            DepthEnd = 2,
            IdCategory = 5002,
            IdTelemetry = 1,
            Value = 1,
            IdEditor = 1,
            IdUserAtStart = 1
        }
    };

    private List<TelemetryDataSaubDto> telemetryDataSaubDtos = new List<TelemetryDataSaubDto> {
        new TelemetryDataSaubDto()
        {
            IdTelemetry = 1,
            DateTime = DateTime.UtcNow.AddMinutes(-30),
            AxialLoad = 1,
            AxialLoadLimitMax = 1,
            AxialLoadSp = 1,
            BitDepth = 1,
            BlockPosition = 1,
            BlockPositionMax = 1,
            BlockPositionMin = 1,
            BlockSpeed = 1,
            BlockSpeedSp = 1,
            BlockSpeedSpDevelop = 1,
            BlockSpeedSpRotor = 1,
            BlockSpeedSpSlide = 1,
            Flow = 1,
            FlowDeltaLimitMax = 1,
            FlowIdle = 1,
            HookWeight = 1,
            HookWeightIdle = 1,
            HookWeightLimitMax = 1,
            HookWeightLimitMin = 1,
            IdFeedRegulator = 1,
            IdUser = 1,
            Mode = 1,
            Mse = 1,
            MseState = 1,
            Pressure = 1,
            PressureDeltaLimitMax = 1,
            PressureIdle = 1,
            PressureSp = 1,
            PressureSpDevelop = 1,
            PressureSpRotor = 1,
            PressureSpSlide = 1,
            Pump0Flow = 1,
            Pump1Flow = 1,
            Pump2Flow = 1,
            RotorSpeed = 1,
            RotorTorque = 1,
            RotorTorqueIdle = 1,
            RotorTorqueSp = 1,
            RotorTorqueLimitMax = 1,
            WellDepth = 10,
        }
    };

    public DataSaubStatServiceTest()
    {
        telemetryDataCacheMock
            .GetIds(Arg.Any<TelemetryDataRequest>())
            .Returns(idTelemetries);

        dataSaubStatRepositoryMock
            .GetLastsAsync(Arg.Any<int[]>(), Arg.Any<CancellationToken>())
            .Returns(dataSaubStatDtos);

        var telemetrySaubDto = telemetryDataSaubDtos.FirstOrDefault();
        if (telemetrySaubDto != null)
        {
            //заполнение списка телеметрий следующим образом:
            // - всего в списке 6 элементов:
            // - все они попадают в диапазон, определенный датами DateStart и DateEnd соответствующей записи detectedOperation
            // - из этих 6-х записей у 2-х записей меняется параметр,
            // являющийся признаком начала нового интервала (новой записи dataSaubStat)
            // таким образом, в базе данных должно создаться 1 новая запись dataSaubStat (insertedDataSaubStatCount = 1)
            var telemetrySaubDto1 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto1.DateTime = DateTime.UtcNow.AddMinutes(-20);
            telemetryDataSaubDtos.Add(telemetrySaubDto1);

            var telemetrySaubDto2 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto2.DateTime = DateTime.UtcNow.AddMinutes(-10);
            telemetrySaubDto2.RotorTorqueLimitMax = 2;
            telemetryDataSaubDtos.Add(telemetrySaubDto2);

            var telemetrySaubDto3 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto3.DateTime = DateTime.UtcNow.AddMinutes(-8);
            telemetrySaubDto3.RotorTorqueLimitMax = 2;
            telemetryDataSaubDtos.Add(telemetrySaubDto3);

            var telemetrySaubDto4 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto4.DateTime = DateTime.UtcNow.AddMinutes(-6);
            telemetrySaubDto4.RotorTorqueLimitMax = 2;
            telemetryDataSaubDtos.Add(telemetrySaubDto4);

            var telemetrySaubDto5 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto5.DateTime = DateTime.UtcNow.AddMinutes(-4);
            telemetrySaubDto5.RotorTorqueLimitMax = 2;
            telemetryDataSaubDtos.Add(telemetrySaubDto5);

            var telemetrySaubDto6 = telemetrySaubDto.Adapt<TelemetryDataSaubDto>();
            telemetrySaubDto6.DateTime = DateTime.UtcNow.AddMinutes(-2);
            telemetrySaubDto6.RotorTorqueLimitMax = 3;
            telemetrySaubDto6.WellDepth = 11;
            telemetryDataSaubDtos.Add(telemetrySaubDto6);
        }

        dataSaubServiceMock
            .Get(Arg.Any<int>(), Arg.Any<bool>(), Arg.Any<DateTimeOffset>(), Arg.Any<DateTimeOffset>(), Arg.Any<int>(), Arg.Any<CancellationToken>())
            .Returns(telemetryDataSaubDtos);

        dataSaubStatService = new DataSaubStatService(
            dataSaubStatRepositoryMock,
            telemetryDataCacheMock,
            dataSaubServiceMock,
            detectedOperationRepositoryMock);
    }

    [Fact]
    public async Task Create_1DataSaubStatItems_ShouldReturn__Success()
    {
        var insertedDataSaubStatCount = 1;

        detectedOperationRepositoryMock
            .Get(Arg.Any<DetectedOperationByTelemetryRequest>(), Arg.Any<CancellationToken>())
            .Returns(detectedOperationDtos);


        dataSaubStatRepositoryMock
            .InsertRangeAsync(Arg.Any<IEnumerable<DataSaubStatDto>>(), Arg.Any<CancellationToken>())
            .Returns(insertedDataSaubStatCount);

        Action<string, double?> action = (message, percent) =>
        {
            //assert
            Assert.NotNull(percent);
            Assert.InRange(percent.Value, 0.0, 1.0);
        };

        //act
        await dataSaubStatService.CreateStatAsync(Gap, action, CancellationToken.None);

        //assert
        await dataSaubStatRepositoryMock.Received().InsertRangeAsync(
            Arg.Is<IEnumerable<DataSaubStatDto>>(l => l.Count() == insertedDataSaubStatCount),
            Arg.Any<CancellationToken>());
    }



    [Fact]
    public async Task Create_2DataSaubStatItems_ShouldReturn__Success()
    {
        var insertedDataSaubStatCount = 2;

        var detectedOperationDto = detectedOperationDtos.FirstOrDefault();
        if (detectedOperationDto != null)
        {
            var detectedOperationDto1 = detectedOperationDto.Adapt<DetectedOperationDto>();
            detectedOperationDto1.DateStart = DateTimeOffset.UtcNow.AddMinutes(1);
            detectedOperationDto1.DateEnd = DateTimeOffset.UtcNow.AddHours(1);

            detectedOperationDtos.Add(detectedOperationDto1);
        }

        var telemetryDataSaubDto = telemetryDataSaubDtos.LastOrDefault();
        if (telemetryDataSaubDto != null)
        {
            var telemetryDataSaubDto1 = telemetryDataSaubDto.Adapt<TelemetryDataSaubDto>();
            telemetryDataSaubDto1.DateTime = DateTime.UtcNow.AddMinutes(10);
            telemetryDataSaubDto1.WellDepth = telemetryDataSaubDto.WellDepth + 1;

            var telemetryDataSaubDto2 = telemetryDataSaubDto.Adapt<TelemetryDataSaubDto>();
            telemetryDataSaubDto2.DateTime = DateTime.UtcNow.AddMinutes(20);
            telemetryDataSaubDto2.WellDepth = telemetryDataSaubDto1.WellDepth + 1;

            var telemetryDataSaubDto3 = telemetryDataSaubDto.Adapt<TelemetryDataSaubDto>();
            telemetryDataSaubDto3.DateTime = DateTime.UtcNow.AddMinutes(30);
            telemetryDataSaubDto3.WellDepth = telemetryDataSaubDto2.WellDepth + 1;

            telemetryDataSaubDtos.Add(telemetryDataSaubDto1);
            telemetryDataSaubDtos.Add(telemetryDataSaubDto2);
            telemetryDataSaubDtos.Add(telemetryDataSaubDto3);
        }

        detectedOperationRepositoryMock
            .Get(Arg.Any<DetectedOperationByTelemetryRequest>(), Arg.Any<CancellationToken>())
            .Returns(detectedOperationDtos);

        dataSaubStatRepositoryMock
            .InsertRangeAsync(Arg.Any<IEnumerable<DataSaubStatDto>>(), Arg.Any<CancellationToken>())
            .Returns(insertedDataSaubStatCount);

        Action<string, double?> action = (message, percent) =>
        {
            //assert
            Assert.NotNull(percent);
            Assert.InRange(percent.Value, 0.0, 1.0);
        };

        //act
        await dataSaubStatService.CreateStatAsync(Gap, action, CancellationToken.None);

        //assert
        await dataSaubStatRepositoryMock.Received().InsertRangeAsync(
            Arg.Is<IEnumerable<DataSaubStatDto>>(l => l.Count() == insertedDataSaubStatCount),
            Arg.Any<CancellationToken>());
    }
}