using Microsoft.Extensions.DependencyInjection;
using DD.Persistence.Client;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Models;
using Xunit;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Client.Clients;
using Microsoft.Extensions.Logging;

namespace DD.Persistence.IntegrationTests.Controllers;
public class TimestampedSetControllerTest : BaseIntegrationTest
{
    private readonly ITimestampedSetClient client;

    public TimestampedSetControllerTest(WebAppFactoryFixture factory) : base(factory)
    {
        var refitClientFactory = scope.ServiceProvider
           .GetRequiredService<IRefitClientFactory<IRefitTimestampedSetClient>>();
        var logger = scope.ServiceProvider.GetRequiredService<ILogger<TimestampedSetClient>>();

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

    [Fact]
    public async Task InsertRange()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        IEnumerable<TimestampedSetDto> testSets = Generate(10, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));

        // act
        var response = await client.AddRange(idDiscriminator, testSets, CancellationToken.None);

        // assert
        Assert.Equal(testSets.Count(), response);
    }

    [Fact]
    public async Task Get_without_filter()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);

        // act
        var response = await client.Get(idDiscriminator, null, null, 0, int.MaxValue, CancellationToken.None);

        // assert
        Assert.NotNull(response);
        Assert.Equal(count, response.Count());
    }

    [Fact]
    public async Task Get_with_filter_props()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
        string[] props = ["A"];

        // act
        var response = await client.Get(idDiscriminator, null, props, 0, int.MaxValue, new CancellationToken());

        // assert
        Assert.NotNull(response);
        Assert.Equal(count, response.Count());
        foreach (var item in response)
        {
            Assert.Single(item.Set);
            var kv = item.Set.First();
            Assert.Equal("A", kv.Key);
        }
    }

    [Fact]
    public async Task Get_geDate()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        var dateMin = DateTimeOffset.Now;
        var dateMax = DateTimeOffset.Now.AddSeconds(count);
        IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));
        var insertResponse = await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
        var tail = testSets.OrderBy(t => t.Timestamp).Skip(count / 2).Take(int.MaxValue);
        var geDate = tail.First().Timestamp;
        var tolerance = TimeSpan.FromSeconds(1);
        var expectedCount = tail.Count();

        // act
        var response = await client.Get(idDiscriminator, geDate, null, 0, int.MaxValue, CancellationToken.None);

        // assert
        Assert.NotNull(response);
        Assert.Equal(expectedCount, response.Count());
        var minDate = response.Min(t => t.Timestamp);
        Assert.Equal(geDate, geDate, tolerance);
    }

    [Fact]
    public async Task Get_with_skip_take()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
        var expectedCount = count / 2;

        // act
        var response = await client.Get(idDiscriminator, null, null, 2, expectedCount, new CancellationToken());

        // assert
        Assert.NotNull(response);
        Assert.Equal(expectedCount, response.Count());
    }


    [Fact]
    public async Task Get_with_big_skip_take()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        var expectedCount = 1;
        int count = 10 + expectedCount;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);

        // act
        var response = await client.Get(idDiscriminator, null, null, count - expectedCount, count, new CancellationToken());

        // assert
        Assert.NotNull(response);
        Assert.Equal(expectedCount, response.Count());
    }

    [Fact]
    public async Task GetLast()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
        var expectedCount = 8;

        // act
        var response = await client.GetLast(idDiscriminator, null, expectedCount, new CancellationToken());

        // assert
        Assert.NotNull(response);
        Assert.Equal(expectedCount, response.Count());
    }

    [Fact]
    public async Task GetDatesRange()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 10;
        var dateMin = DateTimeOffset.Now;
        var dateMax = DateTimeOffset.Now.AddSeconds(count - 1);
        IEnumerable<TimestampedSetDto> testSets = Generate(count, dateMin.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);
        var tolerance = TimeSpan.FromSeconds(1);

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

        // assert
        Assert.NotNull(response);
        Assert.Equal(dateMin, response.From, tolerance);
        Assert.Equal(dateMax, response.To, tolerance);
    }

    [Fact]
    public async Task Count()
    {
        // arrange
        Guid idDiscriminator = Guid.NewGuid();
        int count = 144;
        IEnumerable<TimestampedSetDto> testSets = Generate(count, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));
        await client.AddRange(idDiscriminator, testSets, CancellationToken.None);

        // act
        var response = await client.Count(idDiscriminator, new CancellationToken());

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

    private static IEnumerable<TimestampedSetDto> Generate(int n, DateTimeOffset from)
    {
        for (int i = 0; i < n; i++)
            yield return new TimestampedSetDto
            (
                from.AddSeconds(i),
                new Dictionary<string, object>{
                    {"A", i },
                    {"B", i * 1.1 },
                    {"C", $"Any{i}" },
                    {"D", DateTimeOffset.Now},
                }
            );
    }
}