using Microsoft.EntityFrameworkCore;
using Tiku.Application.Learning;
using Tiku.Domain.Learning;
using ZLinq;

namespace Tiku.Infrastructure.Learning;

public sealed partial class LearningActivityService
{
    public async Task<LearningStatsItem> GetStatsAsync(
        LearningActor actor,
        CancellationToken cancellationToken = default)
    {
        var answers = dbContext.AnswerRecords.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId &&
                           item.UserId == actor.UserId &&
                           item.IsCurrent &&
                           item.GradingStatus != AnswerGradingStatus.PendingReview &&
                           item.GradingStatus != AnswerGradingStatus.LegacyUnverified);
        return new LearningStatsItem(
            await answers.CountAsync(cancellationToken),
            await answers.CountAsync(item => item.IsCorrect == true, cancellationToken),
            await dbContext.WrongQuestions.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId && item.UserId == actor.UserId && item.ResolvedAt == null,
                cancellationToken),
            await dbContext.FavoriteQuestions.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId && item.UserId == actor.UserId,
                cancellationToken),
            await dbContext.UserWordFavorites.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId && item.UserId == actor.UserId,
                cancellationToken),
            await dbContext.UserWordProgress.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId && item.UserId == actor.UserId,
                cancellationToken),
            await dbContext.PracticeSessions.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId && item.UserId == actor.UserId,
                cancellationToken),
            await dbContext.PracticeSessionReports.AsNoTracking().CountAsync(
                item => item.TenantId == actor.TenantId &&
                        item.UserId == actor.UserId &&
                        item.Status == PracticeReportStatus.Final,
                cancellationToken));
    }

    public async Task<LearningList<LearningTrendItem>> GetTrendAsync(
        LearningActor actor,
        LearningLimitFilter filter,
        CancellationToken cancellationToken = default)
    {
        var since = DateTimeOffset.UtcNow.AddDays(-ResolveLimit(filter.Limit));
        var rows = await dbContext.AnswerRecords.AsNoTracking()
            .Where(item =>
                item.TenantId == actor.TenantId &&
                item.UserId == actor.UserId &&
                item.IsCurrent &&
                item.GradingStatus != AnswerGradingStatus.PendingReview &&
                item.GradingStatus != AnswerGradingStatus.LegacyUnverified &&
                item.AnsweredAt >= since)
            .Select(item => new { item.AnsweredAt, item.IsCorrect })
            .ToArrayAsync(cancellationToken);
        var items = rows
            .AsValueEnumerable()
            .GroupBy(item => DateOnly.FromDateTime(item.AnsweredAt.UtcDateTime))
            .OrderBy(group => group.Key)
            .Select(group => new LearningTrendItem(
                group.Key,
                group.Count(),
                group.Count(item => item.IsCorrect == true),
                group.Count(item => item.IsCorrect == false)))
            .ToArray();
        return new LearningList<LearningTrendItem>(items);
    }

    public async Task<LearningLeaderboardResult> GetLeaderboardAsync(
        LearningActor actor,
        LearningLimitFilter filter,
        CancellationToken cancellationToken = default)
    {
        var rows = await dbContext.AnswerRecords.AsNoTracking()
            .Where(item => item.TenantId == actor.TenantId &&
                           item.IsCurrent &&
                           item.GradingStatus != AnswerGradingStatus.PendingReview &&
                           item.GradingStatus != AnswerGradingStatus.LegacyUnverified)
            .GroupBy(item => item.UserId)
            .Select(group => new
            {
                UserId = group.Key,
                AnswerCount = group.Count(),
                CorrectCount = group.Count(item => item.IsCorrect == true),
                WrongCount = group.Count(item => item.IsCorrect == false)
            })
            .OrderByDescending(item => item.CorrectCount)
            .ThenByDescending(item => item.AnswerCount)
            .Take(ResolveLimit(filter.Limit))
            .ToArrayAsync(cancellationToken);
        var userIds = rows.Select(item => item.UserId).ToArray();
        var names = await dbContext.Users.AsNoTracking()
            .Where(item => userIds.Contains(item.Id))
            .ToDictionaryAsync(item => item.Id, item => item.Name ?? item.Phone, cancellationToken);
        var items = rows
            .Select(item => new LearningLeaderboardItem(
                item.UserId,
                names.GetValueOrDefault(item.UserId),
                item.AnswerCount,
                item.CorrectCount,
                item.WrongCount,
                item.AnswerCount == 0 ? 0 : decimal.Round((decimal)item.CorrectCount / item.AnswerCount, 4)))
            .ToArray();
        return new LearningLeaderboardResult(
            "correct_count",
            "all",
            items,
            items.FirstOrDefault(item => item.UserId == actor.UserId),
            DateTimeOffset.UtcNow);
    }
}