using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Infrastructure.Grades;

public static class ExperimentGradeAggregationService
{
    public static async Task<ExperimentGradeAggregateResult> CalculateAsync(
        AppDbContext db,
        Guid teachingTaskId,
        IReadOnlyCollection<Guid> studentIds,
        CancellationToken cancellationToken)
    {
        var sheets = await db.ExperimentGradeSheets.AsNoTracking()
            .Where(x =>
                x.Status ==
                    Domain.Academic.ExperimentGradeSheetStatus.Published &&
                x.ExperimentProject!.TeachingTaskId == teachingTaskId)
            .OrderBy(x => x.ExperimentProject!.Code)
            .Select(x => new PublishedExperimentSheet(
                x.Id,
                x.ExperimentProject!.Code,
                x.ExperimentProject.Name,
                x.ContributionWeight,
                x.Records.Select(record => new PublishedExperimentScore(
                    record.StudentId,
                    record.TotalScore)).ToList()))
            .AsSplitQuery()
            .ToListAsync(cancellationToken);

        var scores = new Dictionary<Guid, decimal?>();
        foreach (var studentId in studentIds.Distinct())
        {
            decimal weightedTotal = 0;
            decimal totalWeight = 0;
            var complete = sheets.Count > 0;
            foreach (var sheet in sheets)
            {
                var score = sheet.Scores.FirstOrDefault(x =>
                    x.StudentId == studentId);
                if (score?.TotalScore is not decimal totalScore)
                {
                    complete = false;
                    break;
                }
                weightedTotal += totalScore * sheet.ContributionWeight;
                totalWeight += sheet.ContributionWeight;
            }
            scores[studentId] = complete && totalWeight > 0
                ? Math.Round(
                    weightedTotal / totalWeight,
                    1,
                    MidpointRounding.AwayFromZero)
                : null;
        }

        return new ExperimentGradeAggregateResult(
            sheets.Count,
            sheets.Select(x => new ExperimentGradeAggregateProject(
                x.Id,
                x.Code,
                x.Name,
                x.ContributionWeight)).ToList(),
            scores);
    }

    private sealed record PublishedExperimentSheet(
        Guid Id,
        string Code,
        string Name,
        decimal ContributionWeight,
        List<PublishedExperimentScore> Scores);

    private sealed record PublishedExperimentScore(
        Guid StudentId,
        decimal? TotalScore);
}

public sealed record ExperimentGradeAggregateResult(
    int PublishedProjectCount,
    IReadOnlyList<ExperimentGradeAggregateProject> Projects,
    IReadOnlyDictionary<Guid, decimal?> Scores);

public sealed record ExperimentGradeAggregateProject(
    Guid Id,
    string Code,
    string Name,
    decimal ContributionWeight);
