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

namespace AsbCloudWebApi.Tests.UnitTests.Services;

public class WellboreServiceTest
{
	private static WellDto well1 = new()
	{
		Id = 1,
		IdState = 1,
		IdTelemetry = 1,
		LastTelemetryDate = DateTime.Now,
		Caption = "well 1"
	};

	private static WellDto well2 = new()
	{
		Id = 2,
		IdState = 1,
		IdTelemetry = 100,
		LastTelemetryDate = DateTime.Now,
		Caption = "well 2"
	};
	
	private readonly IWellService wellServiceMock = Substitute.For<IWellService>();
	private readonly IWellOperationRepository wellOperationRepositoryMock = Substitute.For<IWellOperationRepository>();
	private readonly ITelemetryDataCache<TelemetryDataSaubDto> telemetryDataCacheMock = Substitute.For<ITelemetryDataCache<TelemetryDataSaubDto>>();

	private readonly WellboreService wellboreService;

	public WellboreServiceTest()
	{
		wellboreService = new WellboreService(wellServiceMock, wellOperationRepositoryMock, telemetryDataCacheMock);
	}

	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_EmptyCollection()
	{
		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.NotNull(result);
		Assert.False(result.Any());
	}

	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_OneWellboreByWellOnly()
	{
		wellServiceMock.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
			.Returns(new [] { well1 });

		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.Single(result);
		var wellbore0 = result.ElementAt(0);
		Assert.Equal(well1.Caption, wellbore0.Well.Caption);
		Assert.Equal(well1.Id, wellbore0.Well.Id);

		Assert.Equal("Ствол 1", wellbore0.Name);
		Assert.Equal(1, wellbore0.Id);

		Assert.Equal(default, wellbore0.DateStart);
		Assert.Equal(default, wellbore0.DateEnd);
		Assert.Equal(default, wellbore0.DepthStart);
		Assert.Equal(default, wellbore0.DepthEnd);
	}

	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_TwoWellboreByTwoWellsOnly()
	{
		wellServiceMock.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
			.Returns(new [] { well1, well2 });

		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.Equal(2, result.Count());
	}

	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_TwoWellboreByWellWithSections()
	{
		wellServiceMock.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
			.Returns(new [] { well1 });

		var section0 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 01),
			DateEnd = new DateTime(2023, 01, 02), DepthStart = 000, DepthEnd = 100
		};
		var section1 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 02),
			DateEnd = new DateTime(2023, 01, 03), DepthStart = 100, DepthEnd = 300
		};
		var section2 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03),
			DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 210
		};
		var section3 = new SectionByOperationsDto
		{
			IdWell = int.MaxValue, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03),
			DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 220
		};
		var section4 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 0, DateStart = new DateTime(2023, 01, 05),
			DateEnd = new DateTime(2023, 01, 06), DepthStart = 150, DepthEnd = 220
		};

		wellOperationRepositoryMock.GetSectionsAsync(Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
			.Returns(new [] { section0, section1, section2, section3, section4, });

		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.Equal(2, result.Count());
		var wellbore0 = result.ElementAt(0);
		Assert.Equal(well1.Caption, wellbore0.Well.Caption);
		Assert.Equal(well1.Id, wellbore0.Well.Id);

		Assert.Equal("Ствол 1", wellbore0.Name);
		Assert.Equal(1, wellbore0.Id);

		Assert.Equal(section0.DateStart, wellbore0.DateStart);
		Assert.Equal(section0.DepthStart, wellbore0.DepthStart);
		Assert.Equal(section1.DateEnd, wellbore0.DateEnd);
		Assert.Equal(section1.DepthEnd, wellbore0.DepthEnd);

		var wellbore1 = result.ElementAt(1);
		Assert.Equal(well1.Caption, wellbore1.Well.Caption);
		Assert.Equal(well1.Id, wellbore1.Well.Id);

		Assert.Equal("Ствол 2", wellbore1.Name);
		Assert.Equal(2, wellbore1.Id);

		Assert.Equal(section2.DateStart, wellbore1.DateStart);
		Assert.Equal(section2.DepthStart, wellbore1.DepthStart);
		Assert.Equal(section2.DateEnd, wellbore1.DateEnd);
		Assert.Equal(section2.DepthEnd, wellbore1.DepthEnd);
	}


	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_OneWellboreByWellWithTelemetry()
	{
		wellServiceMock.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
			.Returns(new [] { well1 });

		var firstCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2000, 01, 01), WellDepth = 0, };
		var lastCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2023, 01, 05), WellDepth = 321, };

		telemetryDataCacheMock.GetOrDefaultFirstLast(Arg.Any<int>())
			.Returns((firstCacheItem, lastCacheItem));

		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.Single(result);
		var wellbore0 = result.ElementAt(0);
		Assert.Equal(well1.Caption, wellbore0.Well.Caption);
		Assert.Equal(well1.Id, wellbore0.Well.Id);

		Assert.Equal("Ствол 1", wellbore0.Name);
		Assert.Equal(1, wellbore0.Id);

		Assert.Equal(firstCacheItem.DateTime, wellbore0.DateStart);
		Assert.Equal(firstCacheItem.WellDepth!.Value, wellbore0.DepthStart);
		Assert.Equal(lastCacheItem.DateTime, wellbore0.DateEnd);
		Assert.Equal(lastCacheItem.WellDepth!.Value, wellbore0.DepthEnd);
	}

	[Fact]
	public async Task GetWellboresAsync_ShouldReturn_TwoWellboreByWellWithAll()
	{
		wellServiceMock.GetAsync(Arg.Any<WellRequest>(), Arg.Any<CancellationToken>())
			.Returns(new [] { well1 });

		var section0 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 01),
			DateEnd = new DateTime(2023, 01, 02), DepthStart = 000, DepthEnd = 100
		};
		var section1 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 02),
			DateEnd = new DateTime(2023, 01, 03), DepthStart = 100, DepthEnd = 300
		};
		var section2 = new SectionByOperationsDto
		{
			IdWell = well1.Id, IdWellSectionType = 0, IdType = 1, DateStart = new DateTime(2023, 01, 03),
			DateEnd = new DateTime(2023, 01, 04), DepthStart = 200, DepthEnd = 210
		};

		wellOperationRepositoryMock.GetSectionsAsync(Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
			.Returns(new [] { section0, section1, section2 });

		var firstCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2000, 01, 01), WellDepth = 0, };
		var lastCacheItem = new TelemetryDataSaubDto { DateTime = new DateTime(2023, 01, 05), WellDepth = 321, };

		telemetryDataCacheMock.GetOrDefaultFirstLast(Arg.Any<int>())
			.Returns((firstCacheItem, lastCacheItem));

		var result = await wellboreService.GetWellboresAsync(new[] { 1 }, CancellationToken.None);

		Assert.Equal(2, result.Count());
		var wellbore0 = result.ElementAt(0);
		Assert.Equal(well1.Caption, wellbore0.Well.Caption);
		Assert.Equal(well1.Id, wellbore0.Well.Id);

		Assert.Equal("Ствол 1", wellbore0.Name);
		Assert.Equal(1, wellbore0.Id);

		Assert.Equal(section0.DateStart, wellbore0.DateStart);
		Assert.Equal(section0.DepthStart, wellbore0.DepthStart);
		Assert.Equal(section1.DateEnd, wellbore0.DateEnd);
		Assert.Equal(section1.DepthEnd, wellbore0.DepthEnd);

		var wellbore1 = result.ElementAt(1);
		Assert.Equal(well1.Caption, wellbore1.Well.Caption);
		Assert.Equal(well1.Id, wellbore1.Well.Id);

		Assert.Equal("Ствол 2", wellbore1.Name);
		Assert.Equal(2, wellbore1.Id);

		Assert.Equal(section2.DateStart, wellbore1.DateStart);
		Assert.Equal(section2.DepthStart, wellbore1.DepthStart);
		Assert.Equal(lastCacheItem.DateTime, wellbore1.DateEnd);
		Assert.Equal(lastCacheItem.WellDepth!.Value, wellbore1.DepthEnd);
	}
}