using System.Linq.Expressions;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;

namespace e_suite.Utilities.Pagination;

public class PaginatedData<T> : IPaginatedData<T>
{
    public long Count { get; set; }
    public int PageSize { get; set; }
    public int Page { get; set; }
    public int TotalPages => Convert.ToInt32(Math.Ceiling(Count / Convert.ToDecimal(PageSize)));
    public IEnumerable<T> Data { get; set; } = null!;
}

public static class PaginatedData
{
    public static async Task<PaginatedData<T>> Paginate<T>(IQueryable<T> queryable, Paging paging, Func<string, Expression<Func<T, object>>> keySelector,
        Func<string, string, Expression<Func<T, bool>>> filterSelector, CancellationToken cancellationToken)
    {
        var filteredData = ApplyFilters(queryable, paging.Filters, filterSelector);

        var paginated = new PaginatedData<T>
        {
            Count = await filteredData.CountAsync(cancellationToken),
            Page = paging.Page,
            PageSize = paging.PageSize
        };

        if (paginated.Page > paginated.TotalPages) paginated.Page = paginated.TotalPages;
        if (paginated.Page < 1) paginated.Page = 1;

        var sortedData = ApplySort(filteredData, paging.SortKey, paging.SortAscending, keySelector);
        paginated.Data = paging.Page == 0 ? sortedData : sortedData.Skip((paginated.Page - 1) * paginated.PageSize).Take(paginated.PageSize);
        return paginated;
    }

    private static IQueryable<T> ApplyFilters<T>(IQueryable<T> queryable, string filters,
        Func<string, string, Expression<Func<T, bool>>> filterSelector)
    {
        if (string.IsNullOrWhiteSpace(filters))
            return queryable;

        var filtersDictionary = JsonSerializer.Deserialize<Dictionary<string, string>>(filters);

        if (filtersDictionary == null)
            return queryable;

        var filteredQueryable = queryable;

        foreach (var filter in filtersDictionary)
        {
            if (!string.IsNullOrWhiteSpace(filter.Value))
            {
                filteredQueryable = filteredQueryable.Where(filterSelector(filter.Key, filter.Value));
            }
        }

        return filteredQueryable;
    }

    private static IQueryable<T> ApplySort<T>(IQueryable<T> sequences, string sortKey, bool sortAscending,
        Func<string, Expression<Func<T, object>>> keySelector)
    {
        if (sortAscending)
            return sequences.OrderBy(keySelector(sortKey));
        return sequences.OrderByDescending(keySelector(sortKey));
    }
}