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

namespace Jiaowu.Api.Infrastructure.Teaching;

public static class TeachingTaskRosterQuery
{
    public static IQueryable<Student> ForTask(AppDbContext db, Guid teachingTaskId) =>
        db.Students.Where(student =>
            (student.Status == StudentStatus.Active &&
             db.TeachingTaskClasses.Any(assignment =>
                 assignment.TeachingTaskId == teachingTaskId &&
                 assignment.AdministrativeClassId == student.AdministrativeClassId)) ||
            db.CourseEnrollments.Any(enrollment =>
                enrollment.StudentId == student.Id &&
                enrollment.Status == CourseEnrollmentStatus.Enrolled &&
                enrollment.CourseSelectionOffering!.TeachingTaskId == teachingTaskId));

    public static IQueryable<Guid> TaskIdsForStudent(AppDbContext db, Guid studentId)
    {
        var classTaskIds = db.Students
            .Where(student =>
                student.Id == studentId &&
                student.Status == StudentStatus.Active)
            .SelectMany(student => db.TeachingTaskClasses
                .Where(assignment =>
                    assignment.AdministrativeClassId ==
                    student.AdministrativeClassId)
                .Select(assignment => assignment.TeachingTaskId));
        var enrolledTaskIds = db.CourseEnrollments
            .Where(enrollment =>
                enrollment.StudentId == studentId &&
                enrollment.Status == CourseEnrollmentStatus.Enrolled)
            .Select(enrollment =>
                enrollment.CourseSelectionOffering!.TeachingTaskId);
        return classTaskIds.Concat(enrolledTaskIds).Distinct();
    }

    public static async Task<IReadOnlyList<TeachingTaskRosterEntry>> LoadForTasksAsync(
        AppDbContext db,
        IEnumerable<Guid> teachingTaskIds,
        CancellationToken cancellationToken)
    {
        var taskIds = teachingTaskIds.Distinct().ToArray();
        if (taskIds.Length == 0) return [];

        var assignments = await db.TeachingTaskClasses.AsNoTracking()
            .WhereIn(taskIds, x => x.TeachingTaskId)
            .Select(x => new
            {
                x.TeachingTaskId,
                x.AdministrativeClassId
            })
            .ToListAsync(cancellationToken);

        var classIds = assignments
            .Select(x => x.AdministrativeClassId)
            .Distinct()
            .ToArray();
        var classStudents = classIds.Length == 0
            ? []
            : await db.Students.AsNoTracking()
                .Where(x => x.Status == StudentStatus.Active)
                .WhereIn(classIds, x => x.AdministrativeClassId)
                .Select(x => new RosterStudent(
                    x.Id,
                    x.StudentNumber,
                    x.Name,
                    x.AdministrativeClassId,
                    x.AdministrativeClass!.Name))
                .ToListAsync(cancellationToken);

        var enrolledStudents = await db.CourseEnrollments.AsNoTracking()
            .Where(x => x.Status == CourseEnrollmentStatus.Enrolled)
            .WhereIn(taskIds, x => x.CourseSelectionOffering!.TeachingTaskId)
            .Select(x => new TeachingTaskRosterEntry(
                x.CourseSelectionOffering!.TeachingTaskId,
                x.StudentId,
                x.Student!.StudentNumber,
                x.Student.Name,
                x.Student.AdministrativeClass!.Name))
            .ToListAsync(cancellationToken);

        var result = enrolledStudents.ToDictionary(
            x => (x.TeachingTaskId, x.StudentId));
        var studentsByClass = classStudents.ToLookup(x => x.AdministrativeClassId);
        foreach (var assignment in assignments)
        {
            foreach (var student in studentsByClass[assignment.AdministrativeClassId])
            {
                result.TryAdd(
                    (assignment.TeachingTaskId, student.StudentId),
                    new TeachingTaskRosterEntry(
                        assignment.TeachingTaskId,
                        student.StudentId,
                        student.StudentNumber,
                        student.Name,
                        student.ClassName));
            }
        }

        return result.Values
            .OrderBy(x => x.TeachingTaskId)
            .ThenBy(x => x.StudentNumber)
            .ToList();
    }

    private sealed record RosterStudent(
        Guid StudentId,
        string StudentNumber,
        string Name,
        Guid AdministrativeClassId,
        string ClassName);
}

public sealed record TeachingTaskRosterEntry(
    Guid TeachingTaskId,
    Guid StudentId,
    string StudentNumber,
    string Name,
    string ClassName);
