using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Database.Model;
using DD.Persistence.Models;
using DD.Persistence.Models.Requests;
using Xunit;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients;
using Microsoft.Extensions.Logging;
using Refit;
using System.Net.Http;

namespace DD.Persistence.IntegrationTests.Controllers;
public class ChangeLogControllerTest : BaseIntegrationTest
{
    private readonly IChangeLogClient client;
    private static readonly Random generatorRandomDigits = new();

    public ChangeLogControllerTest(WebAppFactoryFixture factory) : base(factory)
    {
        var refitClientFactory = scope.ServiceProvider
            .GetRequiredService<IRefitClientFactory<IRefitChangeLogClient>>();
        var logger = scope.ServiceProvider.GetRequiredService<ILogger<ChangeLogClient>>();

        client = scope.ServiceProvider
            .GetRequiredService<IChangeLogClient>();
    }

    [Fact]
    public async Task ClearAndInsertRange_InEmptyDb()
    {
        // arrange
        dbContext.CleanupDbSet<ChangeLog>();

        var idDiscriminator = Guid.NewGuid();
        var dtos = Generate(2);

        // act
        var result = await client.ClearAndAddRange(idDiscriminator, dtos, new CancellationToken());

        // assert
        Assert.Equal(2, result);
    }

    [Fact]
    public async Task ClearAndInsertRange_InNotEmptyDb()
    {
        // arrange
        var insertedCount = 10;
        var createdResult = CreateChangeLogItems(insertedCount, (-15, 15));
        var idDiscriminator = createdResult.Item1;
        var dtos = createdResult.Item2.Select(e => e.Adapt<DataWithWellDepthAndSectionDto>());

        // act
        var result = await client.ClearAndAddRange(idDiscriminator, dtos, new CancellationToken());

        // assert
        Assert.Equal(insertedCount * 2, result);
    }

    [Fact]
    public async Task Add_returns_success()
    {
        // arrange
        var count = 1;
        var idDiscriminator = Guid.NewGuid();
        var dtos = Generate(count);
        var dto = dtos.FirstOrDefault()!;

        // act
        var result = await client.Add(idDiscriminator, dto, new CancellationToken());

        // assert
        Assert.Equal(count, result);
    }

    [Fact]
    public async Task AddRange_returns_success()
    {
        // arrange
        var count = 3;
        var idDiscriminator = Guid.NewGuid();
        var dtos = Generate(count);

        // act
        var result = await client.AddRange(idDiscriminator, dtos, new CancellationToken());

        // assert
        Assert.Equal(count, result);
    }

    [Fact]
    public async Task Update_returns_success()
    {
        // arrange
        dbContext.CleanupDbSet<ChangeLog>();

        var idDiscriminator = Guid.NewGuid();
        var dtos = Generate(1);
        var dto = dtos.FirstOrDefault()!;
        var result = await client.Add(idDiscriminator, dto, new CancellationToken());

        var entity = dbContext.ChangeLog
            .Where(x => x.IdDiscriminator == idDiscriminator)
            .FirstOrDefault();
        dto = entity.Adapt<DataWithWellDepthAndSectionDto>();
        dto.DepthEnd += 10;

        // act
        result = await client.Update(dto, new CancellationToken());

        // assert
        Assert.Equal(2, result);

        var dateBegin = DateTimeOffset.UtcNow.AddDays(-1);
        var dateEnd = DateTimeOffset.UtcNow.AddDays(1);

        var changeLogResult = await client.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, new CancellationToken());
        Assert.NotNull(changeLogResult);

        var obsoleteDto = changeLogResult
            .Where(e => e.Obsolete.HasValue)
            .FirstOrDefault();

        var activeDto = changeLogResult
            .Where(e => !e.Obsolete.HasValue)
            .FirstOrDefault();

        if (obsoleteDto == null || activeDto == null)
        {
            Assert.Fail();
            return;
        }

        Assert.Equal(activeDto.Id, obsoleteDto.IdNext);

    }

    [Fact]
    public async Task UpdateRange_returns_success()
    {
        // arrange
        var count = 2;
        var dtos = Generate(count);
        var entities = dtos.Select(d => d.Adapt<ChangeLog>()).ToArray();
        dbContext.ChangeLog.AddRange(entities);
        dbContext.SaveChanges();

        dtos = entities.Select(c => new DataWithWellDepthAndSectionDto()
        {
            DepthEnd = c.DepthEnd + 10,
            DepthStart = c.DepthStart + 10,
            Id = c.Id,
            IdSection = c.IdSection,
            Value = c.Value
        }).ToArray();

        // act
        var result = await client.UpdateRange(dtos, new CancellationToken());

        // assert
        Assert.Equal(count * 2, result);
    }

    [Fact]
    public async Task Delete_returns_success()
    {
        // arrange
        var dtos = Generate(1);
        var dto = dtos.FirstOrDefault()!;
        var entity = dto.Adapt<ChangeLog>();
        dbContext.ChangeLog.Add(entity);
        dbContext.SaveChanges();

        // act
        var result = await client.Delete(entity.Id, new CancellationToken());

        // assert
        Assert.Equal(1, result);
    }

    [Fact]
    public async Task DeleteRange_returns_success()
    {
        // arrange
        var count = 10;
        var dtos = Generate(count);
        var entities = dtos.Select(d => d.Adapt<ChangeLog>()).ToArray();
        dbContext.ChangeLog.AddRange(entities);
        dbContext.SaveChanges();

        // act
        var ids = entities.Select(e => e.Id);
        var result = await client.DeleteRange(ids, new CancellationToken());

        // assert
        Assert.Equal(count, result);
    }

    [Fact]
    public async Task GetDatesRange_returns_success()
    {
        // arrange
        var changeLogItems = CreateChangeLogItems(3, (-15, 15));
        var idDiscriminator = changeLogItems.Item1;
        var entities = changeLogItems.Item2.OrderBy(e => e.Creation);

        // act
        var result = await client.GetDatesRange(idDiscriminator, new CancellationToken());

        // assert
        Assert.NotNull(result);

        var minDate = entities.First().Creation;
        var maxDate = entities.Last().Creation;

        var expectedMinDate = minDate.ToUniversalTime().ToString();
        var actualMinDate = result.From.ToUniversalTime().ToString();
        Assert.Equal(expectedMinDate, actualMinDate);

        var expectedMaxDate = maxDate.ToUniversalTime().ToString();
        var actualMaxDate = result.To.ToUniversalTime().ToString();
        Assert.Equal(expectedMaxDate, actualMaxDate);
    }

    [Fact]
    public async Task GetByDate_returns_success()
    {
        // arrange
        dbContext.CleanupDbSet<ChangeLog>();

        //создаем записи
        var count = 5;
        var changeLogItems = CreateChangeLogItems(count, (-15, 15));
        var idDiscriminator = changeLogItems.Item1;
        var entities = changeLogItems.Item2;

        //удаляем все созданные записи за исключением первой и второй
        //даты 2-х оставшихся записей должны вернуться в методе GetByDate
        var ids = entities.Select(e => e.Id);
        var idsToDelete = ids.Skip(2);

        var deletedCount = await client.DeleteRange(idsToDelete, new CancellationToken());

        var filterRequest = new SectionPartRequest()
        {
            DepthStart = 0,
            DepthEnd = 1000,
        };

        var paginationRequest = new PaginationRequest()
        {
            Skip = 0,
            Take = 10,
            SortSettings = String.Empty,
        };

        var moment = DateTimeOffset.UtcNow.AddDays(16);
        var result = await client.GetByDate(idDiscriminator, moment, filterRequest, paginationRequest, new CancellationToken());

        Assert.NotNull(result);

        var restEntities = entities.Where(e => !idsToDelete.Contains(e.Id));
        Assert.Equal(restEntities.Count(), result.Count);

        var actualIds = restEntities.Select(e => e.Id);
        var expectedIds = result.Items.Select(e => e.Id);
        Assert.Equivalent(expectedIds, actualIds);
    }

    [Theory]
    [InlineData(5, -15, 15, -20, 20, 10)]
    [InlineData(5, -15, -10, -16, -9, 5)]
    public async Task GetChangeLogForInterval_returns_success(
        int insertedCount,
        int daysBeforeNowChangeLog,
        int daysAfterNowChangeLog,
        int daysBeforeNowFilter,
        int daysAfterNowFilter,
        int changeLogCount)
    {
        // arrange
        dbContext.CleanupDbSet<ChangeLog>();

        //создаем записи
        var count = insertedCount;
        var daysRange = (daysBeforeNowChangeLog, daysAfterNowChangeLog);
        var changeLogItems = CreateChangeLogItems(count, daysRange);
        var idDiscriminator = changeLogItems.Item1;
        var entities = changeLogItems.Item2;

        foreach (var entity in entities)
        {
            entity.DepthEnd += 10;
        }
        var dtos = entities.Select(e => e.Adapt<DataWithWellDepthAndSectionDto>()).ToArray();
        await client.UpdateRange(dtos, new CancellationToken());

        //act
        var dateBegin = DateTimeOffset.UtcNow.AddDays(daysBeforeNowFilter);
        var dateEnd = DateTimeOffset.UtcNow.AddDays(daysAfterNowFilter);
        var result = await client.GetChangeLogForInterval(idDiscriminator, dateBegin, dateEnd, new CancellationToken());

        //assert
        Assert.NotNull(result);
        Assert.Equal(changeLogCount, result.Count());
    }


    private static IEnumerable<DataWithWellDepthAndSectionDto> Generate(int count)
    {
        for (int i = 0; i < count; i++)
            yield return new DataWithWellDepthAndSectionDto()
            {
                Value = new Dictionary<string, object>()
                {
                    { "Key", 1 }
                },
                DepthStart = generatorRandomDigits.Next(1, 5),
                DepthEnd = generatorRandomDigits.Next(5, 15),
                Id = Guid.NewGuid(),
                IdSection = Guid.NewGuid()
            };

    }

    private (Guid, ChangeLog[]) CreateChangeLogItems(int count, (int, int) daysRange)
    {
        var minDayCount = daysRange.Item1;
        var maxDayCount = daysRange.Item2;

        Guid idDiscriminator = Guid.NewGuid();
        var dtos = Generate(count);
        var entities = dtos.Select(d =>
        {
            var entity = d.Adapt<ChangeLog>();
            entity.IdDiscriminator = idDiscriminator;
            entity.Creation = DateTimeOffset.UtcNow.AddDays(generatorRandomDigits.Next(minDayCount, maxDayCount));

            return entity;
        }).ToArray();
        dbContext.ChangeLog.AddRange(entities);
        dbContext.SaveChanges();

        return (idDiscriminator, entities);
    }
}