using DD.Persistence.Client;
using DD.Persistence.Client.Clients;
using DD.Persistence.Client.Clients.Interfaces;
using DD.Persistence.Client.Clients.Interfaces.Refit;
using DD.Persistence.Database.Entity;
using DD.Persistence.Models;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.Text.Json;
using Xunit;

namespace DD.Persistence.IntegrationTests.Controllers;
public class TimestampedValuesControllerTest : BaseIntegrationTest
{
    private static readonly string SystemCacheKey = $"{typeof(ValuesIdentity).FullName}CacheKey";
    private readonly ITimestampedValuesClient timestampedValuesClient;
    private readonly IMemoryCache memoryCache;

    public TimestampedValuesControllerTest(WebAppFactoryFixture factory) : base(factory)
    {
        var refitClientFactory = scope.ServiceProvider
           .GetRequiredService<IRefitClientFactory<IRefitTimestampedValuesClient>>();
        var logger = scope.ServiceProvider.GetRequiredService<ILogger<TimestampedValuesClient>>();

        timestampedValuesClient = scope.ServiceProvider
            .GetRequiredService<ITimestampedValuesClient>();
        memoryCache = scope.ServiceProvider.GetRequiredService<IMemoryCache>();
    }

    [Fact]
    public async Task AddRange_returns_success()
    {
        var discriminatorId = Guid.NewGuid();

        await AddRange(discriminatorId);
    }

    [Fact]
    public async Task Get_returns_success()
    {
        //arrange
        Cleanup();

        var discriminatorId = Guid.NewGuid();

        //act
        var response = await timestampedValuesClient.Get(discriminatorId, null, null, 0, 1, CancellationToken.None);

        //assert
        Assert.Null(response);
    }

    [Fact]

    public async Task Get_AfterSave_returns_success()
    {
        //arrange
        Cleanup();

        var discriminatorId = Guid.NewGuid();
        var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);
        var columnNames = new List<string>() { "A", "C" };
        var skip = 5;
        var take = 5;

        var dtos = await AddRange(discriminatorId);

        //act
        var response = await timestampedValuesClient.Get(discriminatorId, timestampBegin, columnNames, skip, take, CancellationToken.None);

        //assert
        Assert.NotNull(response);
        Assert.NotEmpty(response);

        var actualCount = response.Count();
        Assert.Equal(take, actualCount);

        var actualColumnNames = response.SelectMany(e => e.Values.Keys).Distinct().ToList();
        Assert.Equal(columnNames, actualColumnNames);

        var expectedValueKind = JsonValueKind.Number;
        var actualValueKind = ((JsonElement) response.First().Values["A"]).ValueKind;
        Assert.Equal(expectedValueKind, actualValueKind);

        expectedValueKind = JsonValueKind.String;
        actualValueKind = ((JsonElement)response.First().Values["C"]).ValueKind;
        Assert.Equal(expectedValueKind, actualValueKind);
    }

    [Fact]
    public async Task GetGtDate_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var timestampBegin = DateTimeOffset.UtcNow.AddDays(-1);

        //act
        var response = await timestampedValuesClient.GetGtDate(discriminatorId, timestampBegin, CancellationToken.None);

        //assert
        Assert.Null(response);
    }

    [Fact]
    public async Task GetGtDate_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var dtos = await AddRange(discriminatorId);
        var timestampBegin = DateTimeOffset.UtcNow.AddSeconds(-5);

        //act
        var response = await timestampedValuesClient.GetGtDate(discriminatorId, timestampBegin, CancellationToken.None);

        //assert
        Assert.NotNull(response);
        Assert.NotEmpty(response);

        var expectedCount = dtos.Count(dto => dto.Timestamp.ToUniversalTime() > timestampBegin);
        var actualCount = response.Count();
        Assert.Equal(expectedCount, actualCount);
    }

    [Fact]
    public async Task GetFirst_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var take = 1;

        //act
        var response = await timestampedValuesClient.GetFirst(discriminatorId, take, CancellationToken.None);

        //assert
        Assert.Null(response);
    }

    [Fact]
    public async Task GetFirst_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var dtos = await AddRange(discriminatorId);
        var take = 1;

        //act
        var response = await timestampedValuesClient.GetFirst(discriminatorId, take, CancellationToken.None);

        //assert
        Assert.NotNull(response);
        Assert.NotEmpty(response);

        var expectedTimestampString = dtos
            .OrderBy(dto => dto.Timestamp)
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        var actualTimestampString = response
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        Assert.Equal(expectedTimestampString, actualTimestampString);
    }

    [Fact]
    public async Task GetLast_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var take = 1;

        //act
        var response = await timestampedValuesClient.GetLast(discriminatorId, take, CancellationToken.None);

        //assert
        Assert.Null(response);
    }

    [Fact]
    public async Task GetLast_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var dtos = await AddRange(discriminatorId);
        var take = 1;

        //act
        var response = await timestampedValuesClient.GetLast(discriminatorId, take, CancellationToken.None);

        //assert
        Assert.NotNull(response);
        Assert.NotEmpty(response);

        var expectedTimestampString = dtos
            .OrderByDescending(dto => dto.Timestamp)
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        var actualTimestampString = response
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        Assert.Equal(expectedTimestampString, actualTimestampString);
    }

    [Fact]
    public async Task GetResampledData_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var timestampBegin = DateTimeOffset.UtcNow;

        //act
        var response = await timestampedValuesClient.GetResampledData(discriminatorId, timestampBegin);

        //assert
        Assert.Null(response);
    }

    [Fact]
    public async Task GetResampledData_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var count = 2048;
        var timestampBegin = DateTimeOffset.UtcNow;
        var dtos = await AddRange(discriminatorId, count);
        

        //act
        var response = await timestampedValuesClient.GetResampledData(discriminatorId, timestampBegin, count);

        //assert
        Assert.NotNull(response);
        Assert.NotEmpty(response);

        var expectedCount = count / 2;
        var actualCount = response.Count();
        Assert.Equal(expectedCount, actualCount);
    }

    [Fact]
    public async Task Count_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();

        //act
        var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None);

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

    [Fact]
    public async Task Count_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var dtos = await AddRange(discriminatorId);

        //act
        var response = await timestampedValuesClient.Count(discriminatorId, CancellationToken.None);

        //assert
        var expectedCount = dtos.Count();
        Assert.Equal(expectedCount, response);
    }

    [Fact]
    public async Task GetDatesRange_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();

        //act
        var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None);

        //assert
        Assert.Null(response);
    }

    [Fact]
    public async Task GetDatesRange_AfterSave_returns_success()
    {
        //arrange
        Cleanup();
        var discriminatorId = Guid.NewGuid();
        var dtos = await AddRange(discriminatorId);

        //act
        var response = await timestampedValuesClient.GetDatesRange(discriminatorId, CancellationToken.None);

        //assert
        Assert.NotNull(response);

        var expectedDateFromString = dtos
            .OrderBy(dto => dto.Timestamp)
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        var actualDateFromString = response.From
            .ToUniversalTime()
            .ToString();
        Assert.Equal(expectedDateFromString, actualDateFromString);

        var expectedDateToString = dtos
            .OrderByDescending(dto => dto.Timestamp)
            .First().Timestamp
            .ToUniversalTime()
            .ToString();
        var actualDateToString = response.To
            .ToUniversalTime()
            .ToString();
        Assert.Equal(expectedDateToString, actualDateToString);
    }

    private async Task<IEnumerable<TimestampedValuesDto>> AddRange(Guid discriminatorId, int countToCreate = 10)
    {
        // arrange
        IEnumerable<TimestampedValuesDto> generatedDtos = Generate(countToCreate, DateTimeOffset.Now.ToOffset(TimeSpan.FromHours(7)));

        // act
        var response = await timestampedValuesClient.AddRange(discriminatorId, generatedDtos, CancellationToken.None);

        // assert
        Assert.Equal(generatedDtos.Count(), response);

        return generatedDtos;
    }

    private static IEnumerable<TimestampedValuesDto> Generate(int countToCreate, DateTimeOffset from)
    {
        var result = new List<TimestampedValuesDto>();
        for (int i = 0; i < countToCreate; i++)
        {
            var values = new Dictionary<string, object>()
            {
                { "A", i },
                { "B", i * 1.1 },
                { "C", $"Any{i}" },
                { "D", DateTimeOffset.Now },
            };

            yield return new TimestampedValuesDto()
            {
                Timestamp = from.AddSeconds(i),
                Values = values
            };
        }
    }

    private void Cleanup()
    {
        memoryCache.Remove(SystemCacheKey);
        dbContext.CleanupDbSet<TimestampedValues>();
        dbContext.CleanupDbSet<ValuesIdentity>();
    }
}