using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.Trajectory;
using AsbCloudApp.Repositories;
using AsbCloudInfrastructure.Services.Trajectory;
using NSubstitute;
using Xunit;

namespace AsbCloudInfrastructure.Tests.Services;

public class TrajectoryVisualizationServiceTest
{
    private readonly ITrajectoryEditableRepository<TrajectoryGeoPlanDto> trajectoryPlanRepositoryMock = Substitute.For<ITrajectoryEditableRepository<TrajectoryGeoPlanDto>>();
    private readonly ITrajectoryEditableRepository<TrajectoryGeoFactDto> trajectoryFactRepositoryMock = Substitute.For<ITrajectoryEditableRepository<TrajectoryGeoFactDto>>();
    private readonly ITrajectoryNnbRepository trajectoryNnbRepositoryMock = Substitute.For<ITrajectoryNnbRepository>();

    private readonly TrajectoryService trajectoryService;

    public TrajectoryVisualizationServiceTest()
    {
        trajectoryService = new TrajectoryService(trajectoryPlanRepositoryMock, trajectoryFactRepositoryMock, trajectoryNnbRepositoryMock);
    }

    [Fact]
    public async Task GetTrajectoryAsync_ShouldReturn_SameCounts()
    {
        //arrange
        var plannedTrajectory = new TrajectoryGeoPlanDto[]
        {
            new() { WellboreDepth = 0d, ZenithAngle = 0d, AzimuthGeo = 0d },
            new() { WellboreDepth = 0d, ZenithAngle = 0d, AzimuthGeo = 10d },
            new() { WellboreDepth = 0d, ZenithAngle = 30d, AzimuthGeo = 20d },
            new() { WellboreDepth = 30d, ZenithAngle = 0d, AzimuthGeo = 30d },
            new() { WellboreDepth = 30d, ZenithAngle = 90d, AzimuthGeo = 40d },
            new() { WellboreDepth = 0d, ZenithAngle = 0d, AzimuthGeo = 50d },
        };

        var actualTrajectory = new TrajectoryGeoFactDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 30, ZenithAngle = 30, AzimuthGeo = 10 },
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 20 },
        };

        var nnbTrajectory = new TrajectoryGeoFactDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 30, ZenithAngle = 30, AzimuthGeo = 10 },
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 20 },
            new() { WellboreDepth = 0, ZenithAngle = 10, AzimuthGeo = 20 },
        };

        trajectoryPlanRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(plannedTrajectory);

        trajectoryFactRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(actualTrajectory);

        trajectoryNnbRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(nnbTrajectory);

        //act
        var result = await trajectoryService.GetTrajectoryCartesianAsync(1, CancellationToken.None);

        //assert
        Assert.Equal(plannedTrajectory.Count(), result.Plan?.Count());
        Assert.Equal(actualTrajectory.Count(), result.FactManual?.Count());
        Assert.Equal(nnbTrajectory.Count(), result.FactNnb?.Count());
    }

    [Fact]
    public async Task GetTrajectoryAsync_ShouldReturn_StraightBore()
    {
        //arrange
        var plannedTrajectory = new TrajectoryGeoPlanDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 30, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 50, ZenithAngle = 0, AzimuthGeo = 0 },
        };

        var actualTrajectory = new TrajectoryGeoFactDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 30, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 50, ZenithAngle = 0, AzimuthGeo = 0 },
        };

        var nnbTrajectory = new TrajectoryGeoFactDto[]
         {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 30, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 50, ZenithAngle = 0, AzimuthGeo = 0 },
         };

        trajectoryPlanRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(plannedTrajectory);

        trajectoryFactRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(actualTrajectory);

        trajectoryNnbRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(nnbTrajectory);

        //act
        var result = await trajectoryService.GetTrajectoryCartesianAsync(1, CancellationToken.None);

        //assert
        var lastPointPlan = result.Plan!.Last();
        var lastPointFact = result.FactManual!.Last();
        var lastPointNnb = result.FactNnb!.Last();

        Assert.Equal(0d, lastPointPlan.X, 0.1d);
        Assert.Equal(-50d, lastPointPlan.Y, 0.1d);
        Assert.Equal(0d, lastPointPlan.Z, 0.1d);

        Assert.Equal(0d, lastPointFact.X, 0.1d);
        Assert.Equal(-50d, lastPointFact.Y, 0.1d);
        Assert.Equal(0d, lastPointFact.Z, 0.1d);

        Assert.Equal(0d, lastPointNnb.X, 0.1d);
        Assert.Equal(-50d, lastPointNnb.Y, 0.1d);
        Assert.Equal(0d, lastPointNnb.Z, 0.1d);
    }

    [Fact]
    public async Task GetTrajectoryAsync_ShouldReturn_Match()
    {
        //arrange
        const double tolerancePlan = 0.001d;
        const double toleranceFact = 0.001d;

        var plannedTrajectory = new TrajectoryGeoPlanDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 10, ZenithAngle = 30, AzimuthGeo = 30 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
        };

        var actualTrajectory = new TrajectoryGeoFactDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 10, ZenithAngle = 30, AzimuthGeo = 30 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
        };

        var nnbTrajectory = new TrajectoryGeoFactDto[]
        {
            new() { WellboreDepth = 0, ZenithAngle = 0, AzimuthGeo = 0 },
            new() { WellboreDepth = 10, ZenithAngle = 30, AzimuthGeo = 30 },
            new() { WellboreDepth = 20, ZenithAngle = 0, AzimuthGeo = 0 },
        };


        trajectoryPlanRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(plannedTrajectory);

        trajectoryFactRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(actualTrajectory);

        trajectoryNnbRepositoryMock.GetAsync(Arg.Any<int>(), Arg.Any<CancellationToken>())
            .ReturnsForAnyArgs(nnbTrajectory);

        //act
        var result = await trajectoryService.GetTrajectoryCartesianAsync(1, CancellationToken.None);

        //assert
        var lastPointPlan = result.Plan!.Last();
        var lastPointFact = result.FactManual!.Last();
        var lastPointNnb = result.FactManual!.Last();

        Assert.InRange(lastPointPlan.Z, -10 - tolerancePlan, 0 - tolerancePlan);
        Assert.InRange(lastPointPlan.Y, -20 - tolerancePlan, -10 + tolerancePlan);
        Assert.InRange(lastPointPlan.X, 0 + tolerancePlan, 10 - tolerancePlan);

        Assert.InRange(lastPointFact.Z, -10 - toleranceFact, 0 - toleranceFact);
        Assert.InRange(lastPointFact.Y, -20 - toleranceFact, -10 + toleranceFact);
        Assert.InRange(lastPointFact.X, 0 + toleranceFact, 10 - toleranceFact);

        Assert.InRange(lastPointNnb.Z, -10 - toleranceFact, 0 - toleranceFact);
        Assert.InRange(lastPointNnb.Y, -20 - toleranceFact, -10 + toleranceFact);
        Assert.InRange(lastPointNnb.X, 0 + toleranceFact, 10 - toleranceFact);
    }
}