using AsbCloudApp.Requests;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Repository;
using AsbCloudInfrastructure.Services.ProcessMaps.Report;
using AsbCloudInfrastructure.Services;
using NSubstitute;
using ProtoBuf.Meta;
using SignalRSwaggerGen.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using AsbCloudApp.Repositories;
using AsbCloudApp.Data;
using AsbCloudApp.Data.WellOperation;
using AsbCloudApp.Services;

namespace AsbCloudWebApi.Tests.Services.WellCompositeOperation
{
    public class WellCompositeOperationServiceTest
    {
        private WellCompositeOperationService service;

        private ICrudRepository<WellSectionTypeDto> wellSectionTypeRepository
            = Substitute.For<ICrudRepository<WellSectionTypeDto>>();
        private IWellOperationCategoryRepository wellOperationCategoryRepository
            = Substitute.For<IWellOperationCategoryRepository>();
        private IWellOperationRepository wellOperationRepository
           = Substitute.For<IWellOperationRepository>();

        

        private readonly static IEnumerable<WellOperationCategoryDto> operationCategories = new List<WellOperationCategoryDto>()
        {
            new(){Id = 5096, Name = "Шаблонирование перед спуском"},
            new(){Id = 5008, Name = "Шаблонировка во время бурения"},
            new(){Id = 5013, Name = "Подъем КНБК"},
            new(){Id = 5003, Name = "Бурение ротором"},
            new(){Id = 5036, Name = "Промывка"},
            new(){Id = 5012, Name = "Подъем инструмента"},
            new(){Id = 5083, Name = "Проработка принудительная"},
        };

        private readonly static IEnumerable<WellSectionTypeDto> sectionTypes = new List<WellSectionTypeDto>()
        {
            new() {Id = 2, Caption = "Направление", Order = 0},
            new() {Id = 3, Caption = "Кондуктор", Order = 1},
            new() {Id = 31, Caption = "Техническая колонна", Order = 2}
        };

        private readonly static IEnumerable<WellOperationDto> wellOperations1 = new List<WellOperationDto>()
        {
            new()
            {
                DepthStart = 50,
                DurationHours = 1,
                IdCategory = 5096,
                IdWell = 55,
                IdWellSectionType = 2,
                OperationCategoryName = "Шаблонирование перед спуском",
                WellSectionTypeCaption = "Направление"                
            },
            new()
            {
                DepthStart = 84,
                DurationHours = 1,
                IdCategory = 5008,
                IdWell = 64,
                IdWellSectionType = 2,
                OperationCategoryName = "Шаблонировка во время бурения",
                WellSectionTypeCaption = "Направление"
            }
        };

        private readonly static IEnumerable<WellOperationDto> wellOperations2 = new List<WellOperationDto>()
        {
            new()
            {
                DepthStart = 10,
                DurationHours = 1.5,
                IdCategory = 5003,
                IdWell = 55,
                IdWellSectionType = 2,
                OperationCategoryName = "Бурение ротором",
                WellSectionTypeCaption = "Направление"
            },
            new()
            {
                DepthStart = 20,
                DurationHours = 3.5,
                IdCategory = 5003,
                IdWell = 64,
                IdWellSectionType = 2,
                OperationCategoryName = "Бурение ротором",
                WellSectionTypeCaption = "Направление"
            }
        };

        private readonly static IEnumerable<WellOperationDto> wellOperations3 = new List<WellOperationDto>()
        {
            new()
            {
                DepthStart = 1372,
                DurationHours = 3,
                IdCategory = 5036,
                IdWell = 55,
                IdWellSectionType = 3,
                OperationCategoryName = "Промывка",
                WellSectionTypeCaption = "Кондуктор"
            },
            new()
            {
                DepthStart = 1435,
                DurationHours = 4,
                IdCategory = 5036,
                IdWell = 64,
                IdWellSectionType = 3,
                OperationCategoryName = "Промывка",
                WellSectionTypeCaption = "Кондуктор"
            }
        };

        private readonly static IEnumerable<WellOperationDto> wellOperations4 = new List<WellOperationDto>()
        {
            new()
            {
                DepthStart = 1000,
                DurationHours = 10,
                IdCategory = 5012,
                IdWell = 55,
                IdWellSectionType = 31,
                OperationCategoryName = "Подъем инструмента",
                WellSectionTypeCaption = "Техническая колонна"
            },
            new()
            {
                DepthStart = 500,
                DurationHours = 5,
                IdCategory = 5083,
                IdWell = 55,
                IdWellSectionType = 31,
                OperationCategoryName = "Проработка принудительная",
                WellSectionTypeCaption = "Техническая колонна"
            },
            new()
            {
                DepthStart = 600,
                DurationHours = 5,
                IdCategory = 5083,
                IdWell = 64,
                IdWellSectionType = 31,
                OperationCategoryName = "Проработка принудительная",
                WellSectionTypeCaption = "Техническая колонна"
            }
        };

        public WellCompositeOperationServiceTest()
        {

            wellSectionTypeRepository.GetAllAsync(Arg.Any<CancellationToken>())
                .Returns(sectionTypes);

            wellOperationCategoryRepository.Get(Arg.Any<bool>())
                .Returns(operationCategories);

            service = new WellCompositeOperationService(
                wellSectionTypeRepository, 
                wellOperationCategoryRepository,
                wellOperationRepository);
        }

        /// <summary>
        /// На вход подаются 2 операции с одинаковыми секциями (id = 2), но разными категориями (ids = 5096, 5008) и ключами скважин (ids = 55, 64)
        /// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
        /// Операция должна иметь категорию 5013 для всех трех скважин
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task GetAsync_return_composite_and_others_with_category_5013()
        {
            // arrange
            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .Returns(wellOperations1);

            var idsWell = new List<int>() { 55, 64 }; 

            // act
            var result = await service.GetAsync(idsWell, CancellationToken.None);

            // assert
            var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
            Assert.NotNull(compositeWellOperation);
            Assert.Equal(5013, compositeWellOperation.IdCategory);
            Assert.Equal(1, compositeWellOperation.DurationHours);
            Assert.Equal(84, compositeWellOperation.DepthStart);

            var currentWellOperations = result.SelectMany(o => o.Values.Where(o => o.IdWell != 0));
            var categories = currentWellOperations.Select(o => o.IdCategory).Distinct();
            Assert.NotNull(categories);
            Assert.Single(categories);
            Assert.Equal(5013, categories.First());

            var categoryName = currentWellOperations.Select(o => o.OperationCategoryName).First();
            Assert.Equal("Подъем КНБК", categoryName);
        }

        /// <summary>
        /// На вход подаются 2 операции с одинаковыми секциями (id = 2) и категориями (id = 5003), но разными ключами скважин (ids = 55, 64)
        /// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
        /// Операция композитной скважины должна содержать данные той операции, которая содержит минимальный duration_hours 
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task GetAsync_return_composite_with_minimum_depth_start()
        {
            // arrange
            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .Returns(wellOperations2);

            var idsWell = new List<int>() { 55, 64 };

            // act
            var result = await service.GetAsync(idsWell, CancellationToken.None);

            // assert
            var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
            Assert.NotNull(compositeWellOperation);
            Assert.Equal(5003, compositeWellOperation.IdCategory);
            Assert.Equal(1.5, compositeWellOperation.DurationHours);
            Assert.Equal(10, compositeWellOperation.DepthStart);
        }

        /// <summary>
        /// На вход подаются 2 операции с одинаковыми секциями (id = 3) и категориями (id = 5036), но разными ключами скважин (ids = 55, 64)
        /// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
        /// Операция композитной скважины должна содержать данные той операции, которая содержит минимальный duration_hours 
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task GetAsync_return_data3()
        {
            // arrange
            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .Returns(wellOperations3);

            var idsWell = new List<int>() { 55, 64 };

            // act
            var result = await service.GetAsync(idsWell, CancellationToken.None);

            // assert
            var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
            Assert.NotNull(compositeWellOperation);
            Assert.Equal(5036, compositeWellOperation.IdCategory);
            Assert.Equal(3, compositeWellOperation.DurationHours);
            Assert.Equal(1372, compositeWellOperation.DepthStart);
        }

        /// <summary>
        /// На вход подаются 3 операции с одинаковыми секциями (id = 31), но разными категориями (ids = 5012, 5083) и ключами скважин (ids = 55, 64)
        /// Метод возвращает список из одной операции в разрезе 3-х скважин: 2 текущие скважины и одна композитная
        /// Операция композитной скважины должна содержать:
        ///     данные той операции, которая содержит минимальный duration_hours
        ///     категорию с ключом 5013
        /// Операции по скважине с ключом 55 должны объединиться в одну, 
        ///     при этом их длительность складывается, а depth_start берется минимальный из двух
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task GetAsync_return_data4()
        {
            // arrange
            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .Returns(wellOperations4);

            var idsWell = new List<int>() { 55, 64 };

            // act
            var result = await service.GetAsync(idsWell, CancellationToken.None);

            // assert
            var currentWellOperations = result.SelectMany(o => o.Values.Where(o => o.IdWell != 0));
            var categories = currentWellOperations.Select(o => o.IdCategory).Distinct();
            Assert.NotNull(categories);
            Assert.Single(categories);
            Assert.Equal(5013, categories.First());

            var currentOperationByWell55 = currentWellOperations.Where(o => o.IdWell == 55).FirstOrDefault();
            Assert.NotNull(currentOperationByWell55);
            Assert.Equal(15, currentOperationByWell55.DurationHours);
            Assert.Equal(500, currentOperationByWell55.DepthStart);

            var categoryName = currentWellOperations.Select(o => o.OperationCategoryName).First();
            Assert.Equal("Подъем КНБК", categoryName);

            var compositeWellOperation = result.SelectMany(o => o.Values.Where(o => o.IdWell == 0)).FirstOrDefault();
            Assert.NotNull(compositeWellOperation);
            Assert.Equal(5013, compositeWellOperation.IdCategory);
            Assert.Equal(5, compositeWellOperation.DurationHours);
            Assert.Equal(600, compositeWellOperation.DepthStart);
        }

        /// <summary>
        /// На вход подаются список разных операций с разными ключами скважин (ids = 55, 64)
        /// Метод возвращает список из 4-х операций в разрезе 3-х скважин: 2 текущие скважины и одна композитная
        /// Операция композитной скважины должна содержать глубину забоя = 1372
        /// </summary>
        /// <returns></returns>
        [Fact]
        public async Task GetAsync_return_data5()
        {
            // arrange
            var wellOperations = new List<WellOperationDto>();
            wellOperations.AddRange(wellOperations1);
            wellOperations.AddRange(wellOperations2);
            wellOperations.AddRange(wellOperations3);
            wellOperations.AddRange(wellOperations4);

            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .Returns(wellOperations);

            var idsWell = new List<int>() { 55, 64 };

            // act
            var result = await service.GetAsync(idsWell, CancellationToken.None);

            // assert
            Assert.Equal(4, result.Count());

            var lastOperation = result.Last();
            var lastOperationComposite = lastOperation[0];
            Assert.Equal(1372, lastOperationComposite.DepthStart);
        }
    }
}