using AsbCloudApp.Data;
using AsbCloudApp.Data.WellOperationImport.Options;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.WellOperationImport;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperationImport;
using AsbCloudInfrastructure.Services.WellOperationImport.FileParser;
using NSubstitute;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;

namespace AsbCloudWebApi.Tests.Services.WellOperationExport
{
    public class WellOperationExportServiceTest
    {
        private const int idWell = 4;

        private IWellService wellService;
        private IWellOperationRepository wellOperationRepository;
        private IWellOperationCategoryRepository wellOperationCategoryRepository;
        private IWellOperationImportTemplateService wellOperationImportTemplateService;
        private WellOperationExportService wellOperationExportService;
        private WellOperationImportService wellOperationImportService;
        private IWellOperationExcelParser<WellOperationImportDefaultOptionsDto> wellOperationDefaultExcelParser;

        private readonly WellSectionTypeDto[] sectionTypes = new WellSectionTypeDto[2]
        {
            new WellSectionTypeDto()
            {
                Caption = "1",
                Id = 1,
                Order = 0
            },
            new WellSectionTypeDto()
            {
                Caption = "2",
                Id = 2,
                Order = 1
            }
        };

        private readonly WellOperationCategoryDto[] categories = new WellOperationCategoryDto[2]
        {
            new WellOperationCategoryDto()
            {
               Id = 1,
               IdParent = 1,
               KeyValueName = "1",
               KeyValueUnits = "1",
               Name = "1"
            },
            new WellOperationCategoryDto()
            {
               Id = 2,
               IdParent = 2,
               KeyValueName = "2",
               KeyValueUnits = "2",
               Name = "2"
            }
        };
        private readonly ITestOutputHelper output;

        public WellOperationExportServiceTest(ITestOutputHelper output)
        {
            wellService = Substitute.For<IWellService>();
            wellOperationRepository = Substitute.For<IWellOperationRepository>();
            wellOperationCategoryRepository = Substitute.For<IWellOperationCategoryRepository>();
            wellOperationImportTemplateService = new WellOperationImportTemplateService();
            wellOperationExportService = new WellOperationExportService(wellOperationRepository, wellOperationImportTemplateService, wellOperationCategoryRepository);

            wellOperationImportService = new WellOperationImportService(wellService, wellOperationRepository, wellOperationCategoryRepository);
            wellOperationDefaultExcelParser = new WellOperationDefaultExcelParser();
            this.output = output;

            wellService.GetTimezone(idWell).Returns(new SimpleTimezoneDto()
            {
                Hours = 5
            });
        }

        [Fact]
        public async Task Check_Exported_WellOperations_With_Operations_In_Db()
        {
            var operations = getOperations();

            var localOperations = operations.ToArray();

            foreach (var operation in localOperations)
                operation.Id = 0;

            wellOperationRepository.GetAsync(Arg.Any<WellOperationRequest>(), Arg.Any<CancellationToken>())
                .ReturnsForAnyArgs(localOperations);
            wellOperationRepository.GetSectionTypes().Returns(sectionTypes);
            wellOperationCategoryRepository.Get(false).Returns(categories);

            var stream = await wellOperationExportService.ExportAsync(idWell, CancellationToken.None);

            var options = new WellOperationImportDefaultOptionsDto
            {
                IdType = WellOperation.IdOperationTypePlan
            };
            var sheet = wellOperationDefaultExcelParser.Parse(stream, options);
            var result = wellOperationImportService.Import(idWell, 1, options.IdType, sheet);

            var expected = JsonSerializer.Serialize(localOperations);
            var actual = JsonSerializer.Serialize(result);

            Assert.Equal(expected, actual);
        }

        [Fact]
        public void TestDataContainsNotDefaultProps()
        {
            var initOk = true;
            var operations = getOperations();
            for (int i = 0; i < operations.Length; i++)
            {
                var operation = operations[i];
                var propsWithDefault = GetPropsWithDefaultValue(operation,
                    nameof(WellOperationDto.Id),
                    nameof(WellOperationDto.IdType),
                    nameof(WellOperationDto.IdPlan),
                    nameof(WellOperationDto.IdParentCategory),
                    nameof(WellOperationDto.Day),
                    nameof(WellOperationDto.NptHours),
                    nameof(WellOperationDto.UserName),
                    nameof(WellOperationDto.LastUpdateDate)
                    )
                    .ToArray();

                if (propsWithDefault.Any())
                {
                    initOk = false;
                    foreach (var propertyName in propsWithDefault)
                        output.WriteLine($"{nameof(operations)}[{i}].{propertyName} is default");
                }
            }

            Assert.True(initOk);
        }

        private static IEnumerable<string> GetPropsWithDefaultValue<T>(T dto, params string[] excludeProps)
        {
            IEnumerable<PropertyInfo> props = typeof(T).GetProperties(
                BindingFlags.Public
                | BindingFlags.Instance);

            props = props
                .Where(prop => prop.CanWrite)
                .Where(prop => !excludeProps.Contains(prop.Name));

            foreach (var prop in props)
            {
                var value = prop.GetValue(dto);
                if (prop.PropertyType.IsDefaultValue(value))
                    yield return prop.Name;
            }
        }

        private WellOperationDto[] getOperations()
        {

            var timezone = wellService.GetTimezone(idWell);

            DateTimeOffset GetDate(int days)
            {
                var date = DateTimeOffset.UtcNow.AddDays(days);
                return new DateTimeOffset(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second, TimeSpan.FromHours(timezone.Hours));
            }

            return new WellOperationDto[2] {
                new WellOperationDto() {
                    Id = 5,
                    IdWell = idWell,
                    IdUser = 1,
                    IdType = 0,
                    IdWellSectionType = 1,
                    WellSectionTypeName = "1",
                    IdCategory = 1,
                    CategoryName = "1",
                    CategoryInfo = "CategoryInfo 1",
                    DepthStart = 10,
                    DepthEnd = 20,
                    DateStart = GetDate(days: 0),
                    DurationHours = 10,
                    Comment = "Комментарий 1",
                },
               new WellOperationDto() {
                    Id = 6,
                    IdWell = idWell,
                    IdUser = 1,
                    IdType = 0,
                    IdWellSectionType = 2,
                    WellSectionTypeName = "2",
                    IdCategory = 2,
                    CategoryName = "2",
                    CategoryInfo = "CategoryInfo 2",
                    DepthStart = 20,
                    DepthEnd = 30,
                    DateStart = GetDate(days: 1),
                    DurationHours = 20,
                    Comment = "Комментарий 2",
                }
            };
        }
    }
}