using System.Linq.Expressions;
using MockQueryable;
using NUnit.Framework;

namespace e_suite.Utilities.Pagination.UnitTests;

public class TestData
{
    public string Name = string.Empty;
    public long Id;
}

[TestFixture]
public class PaginateUnitTests
{
    private Expression<Func<TestData, object>> KeySelector(string sortKey)
    {
        return sortKey.ToLowerInvariant() switch
        {
            "id" => x => x.Id,
            _ => x => x.Name
        };
    }

    [Test]
    public async Task Paginate_WhenNoSpecialValues_ReturnsExpectedResults()
    {
        var data = new List<TestData>
        {
            new() {Id =1, Name = "test1"}
        };

        var queryable = data.BuildMock();

        var paging = new Paging();
        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(1));
        Assert.That(result.Data.Count,  Is.EqualTo(1));
    }

    [Test]
    public async Task Paginate_TenItems_ReturnsTenItems()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging();
        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(10));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(1));
        Assert.That(result.Data.Count, Is.EqualTo(10));
    }

    [Test]
    public async Task Paginate_ElevenItems_ReturnsPageOne()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();
        var paging = new Paging();

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(10));
    }

    [Test]
    public async Task Paginate_Page2Requested_ReturnsPageTwo()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 2
        };
        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(2));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(1));
    }

    [Test]
    public async Task Paginate_SortAscendingTrueAndPage2_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 2
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(2));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(9));
        Assert.That(checkItem.Name, Is.EqualTo("test9"));
    }

    [Test]
    public async Task Paginate_SortAscendingFalseAndPage2_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 2,
            SortAscending = false
        };
        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(2));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(1));
        Assert.That(checkItem.Name, Is.EqualTo("test1"));
    }

    [Test]
    public async Task Paginate_SortAscendingFalseByIdAndPage2_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 2,
            SortKey = "id",
            SortAscending = false,
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(2));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(1));
        Assert.That(checkItem.Name, Is.EqualTo("test1"));
    }

    [Test]
    public async Task Paginate_SortAscendingTrueByIdAndPage2_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 2,
            SortKey = "id",
            SortAscending = true,
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(2));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(2));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(11));
        Assert.That(checkItem.Name, Is.EqualTo("test11"));
    }

    [Test]
    public async Task Paginate_SearchFilterContainsId_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Filters = "{\"id\":\"2\"}"
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(1));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(2));
        Assert.That(checkItem.Name, Is.EqualTo("test2"));
    }

    [Test]
    public async Task Paginate_SearchFilterContainsName_ReturnsExpectedData()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Filters = "{\"name\":\"test2\"}"
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(1));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(10));
        Assert.That(result.TotalPages, Is.EqualTo(1));
        Assert.That(result.Data.Count, Is.EqualTo(1));

        var checkItem = result.Data.Single();

        Assert.That(checkItem.Id, Is.EqualTo(2));
        Assert.That(checkItem.Name, Is.EqualTo("test2"));
    }


    [Test]
    public async Task Paginate_RequestingPage0_ReturnsEveryRow()
    {
        var data = new List<TestData>
        {
            new() { Id = 1, Name = "test1" },
            new() { Id = 2, Name = "test2" },
            new() { Id = 3, Name = "test3" },
            new() { Id = 4, Name = "test4" },
            new() { Id = 5, Name = "test5" },
            new() { Id = 6, Name = "test6" },
            new() { Id = 7, Name = "test7" },
            new() { Id = 8, Name = "test8" },
            new() { Id = 9, Name = "test9" },
            new() { Id = 10, Name = "test10" },
            new() { Id = 11, Name = "test11" }
        };

        var queryable = data.BuildMock();

        var paging = new Paging
        {
            Page = 0,
            PageSize = 5
        };

        var result = await PaginatedData.Paginate(queryable, paging, KeySelector, CancellationToken.None);

        Assert.That(result.Count, Is.EqualTo(11));
        Assert.That(result.Page, Is.EqualTo(1));
        Assert.That(result.PageSize, Is.EqualTo(5));
        Assert.That(result.TotalPages, Is.EqualTo(3));
        Assert.That(result.Data.Count, Is.EqualTo(11));
    }
}