using JSMR.Application.Circles.Queries.Search;
using JSMR.Application.Common.Search;
using JSMR.Domain.Entities;
using JSMR.Infrastructure.Common.Queries;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;

namespace JSMR.Infrastructure.Data.Repositories.Circles;

public class CircleQuery
{
    public required Circle Circle { get; init; }
}

public class CircleSearchProvider(AppDbContext context) : SearchProvider<CircleSearchItem, CircleSearchCriteria, CircleSortField, CircleQuery>, ICircleSearchProvider
{
    protected override bool UseSelectIdQuery => false;

    protected override IQueryable<CircleQuery> GetBaseQuery()
    {
        // Project from Circles so we can use correlated subqueries per CircleId.
        var q =
        from c in context.Circles.AsNoTracking()
        select new CircleQuery
        {
            Circle = c
        };

        return q;
    }

    protected override IQueryable<CircleQuery> ApplyFilters(IQueryable<CircleQuery> query, CircleSearchCriteria criteria)
    {
        if (!string.IsNullOrWhiteSpace(criteria.Name))
        {
            var term = $"%{criteria.Name.Trim()}%";

            query = query.Where(x =>
                EF.Functions.Like(x.Circle.Name, term) ||
                EF.Functions.Like(x.Circle.MakerId, term));
        }

        switch (criteria.Status)
        {
            case CircleStatus.NotBlacklisted:
                query = query.Where(x => !x.Circle.Blacklisted);
                break;
            case CircleStatus.Favorited:
                query = query.Where(x => x.Circle.Favorite);
                break;
            case CircleStatus.Blacklisted:
                query = query.Where(x => x.Circle.Blacklisted);
                break;
            case CircleStatus.Spam:
                query = query.Where(x => x.Circle.Spam);
                break;
        }

        return query;
    }

    protected override Expression<Func<CircleQuery, object?>> GetSortExpression(CircleSortField field) => field switch
    {
        CircleSortField.Favorite => x => !x.Circle.Favorite,
        CircleSortField.Blacklisted => x => !x.Circle.Blacklisted,
        CircleSortField.Spam => x => !x.Circle.Spam,
        _ => x => x.Circle.Name
    };

    protected override IEnumerable<(Expression<Func<CircleQuery, object>> Selector, SortDirection Dir)> GetDefaultSortChain()
    {
        yield return (x => x.Circle.Name, SortDirection.Ascending);
        yield return (x => x.Circle.MakerId, SortDirection.Ascending);
    }

    protected override IQueryable<int> GetSelectIdQuery(IOrderedQueryable<CircleQuery> query)
    {
        return query.Select(x => x.Circle.CircleId);
    }

    protected override IQueryable<CircleSearchItem> GetSelectQuery(CircleSearchCriteria criteria, IOrderedQueryable<CircleQuery> query)
    {
        // Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate
        var selected =
            from item in query
            //join vw in context.VoiceWorks.AsNoTracking() on item.LatestProductId equals vw.ProductId into vws
            //from latest in vws.DefaultIfEmpty()
            select new CircleSearchItem
            {
                CircleId = item.Circle.CircleId,
                Name = item.Circle.Name,
                MakerId = item.Circle.MakerId,
                Favorite = item.Circle.Favorite,
                Blacklisted = item.Circle.Blacklisted,
                Spam = item.Circle.Spam,
                // Aggregates
                Downloads = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .Select(v => (int?)v.Downloads)      // make nullable for Sum over empty set
                            .Sum() ?? 0,

                Releases = context.VoiceWorks
                            .Count(v => v.CircleId == item.Circle.CircleId && v.SalesDate != null),

                Pending = context.VoiceWorks
                            .Count(v => v.CircleId == item.Circle.CircleId && v.ExpectedDate != null),

                FirstReleaseDate = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .Select(v => v.SalesDate)
                            .Min(),

                LatestReleaseDate = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .Select(v => v.SalesDate)
                            .Max(),

                // "Latest" by ProductId length, then value
                LatestProductId = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => v.ProductId)
                            .FirstOrDefault(),

                // If you want these two in base query too:
                LatestVoiceWorkHasImage = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => (bool?)v.HasImage)
                            .FirstOrDefault(),

                LatestVoiceWorkSalesDate = context.VoiceWorks
                            .Where(v => v.CircleId == item.Circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => v.SalesDate)
                            .FirstOrDefault(),
                //Downloads = item.Downloads,
                //Releases = item.Releases,
                //Pending = item.Pending,
                //FirstReleaseDate = item.FirstReleaseDate,
                //LatestReleaseDate = item.LatestReleaseDate,
                //LatestProductId = item.LatestProductId,
                //LatestVoiceWorkHasImage = latest != null ? latest.HasImage : (bool?)null,
                //LatestVoiceWorkSalesDate = latest != null ? latest.SalesDate : null
            };

        return selected;
    }

    protected override async Task<Dictionary<int, CircleSearchItem>> GetItems(CircleSearchCriteria criteria, int[] ids)
    {
        // Join to VoiceWorks by LatestProductId to fill HasImage / SalesDate
        var selected =
            from circle in context.Circles.AsNoTracking()
                //join vw in context.VoiceWorks.AsNoTracking() on item.LatestProductId equals vw.ProductId into vws
                //from latest in vws.DefaultIfEmpty()
            where ids.Contains(circle.CircleId)
            select new CircleSearchItem
            {
                CircleId = circle.CircleId,
                Name = circle.Name,
                MakerId = circle.MakerId,
                Favorite = circle.Favorite,
                Blacklisted = circle.Blacklisted,
                Spam = circle.Spam,
                // Aggregates
                Downloads = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .Select(v => (int?)v.Downloads)      // make nullable for Sum over empty set
                            .Sum() ?? 0,

                Releases = context.VoiceWorks
                            .Count(v => v.CircleId == circle.CircleId && v.SalesDate != null),

                Pending = context.VoiceWorks
                            .Count(v => v.CircleId == circle.CircleId && v.ExpectedDate != null),

                FirstReleaseDate = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .Select(v => v.SalesDate)
                            .Min(),

                LatestReleaseDate = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .Select(v => v.SalesDate)
                            .Max(),

                // "Latest" by ProductId length, then value
                LatestProductId = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => v.ProductId)
                            .FirstOrDefault(),

                // If you want these two in base query too:
                LatestVoiceWorkHasImage = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => (bool?)v.HasImage)
                            .FirstOrDefault(),

                LatestVoiceWorkSalesDate = context.VoiceWorks
                            .Where(v => v.CircleId == circle.CircleId)
                            .OrderByDescending(v => v.ProductId.Length)
                            .ThenByDescending(v => v.ProductId)
                            .Select(v => v.SalesDate)
                            .FirstOrDefault(),
                //Downloads = item.Downloads,
                //Releases = item.Releases,
                //Pending = item.Pending,
                //FirstReleaseDate = item.FirstReleaseDate,
                //LatestReleaseDate = item.LatestReleaseDate,
                //LatestProductId = item.LatestProductId,
                //LatestVoiceWorkHasImage = latest != null ? latest.HasImage : (bool?)null,
                //LatestVoiceWorkSalesDate = latest != null ? latest.SalesDate : null
            };

        return await selected.ToDictionaryAsync(x => x.CircleId);
    }
}