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

namespace Jiaowu.Api.Infrastructure.Exams;

public sealed class MakeupExamEligibilityService(AppDbContext db)
{
    public async Task<List<EligibleStudent>> GetEligibleStudentsAsync(
        Guid teachingTaskId,
        CancellationToken cancellationToken)
    {
        var gradeSheet = await db.GradeSheets.AsNoTracking()
            .Where(x => x.TeachingTaskId == teachingTaskId)
            .Select(x => new { x.Id, x.TeachingTask!.AcademicTermId })
            .FirstOrDefaultAsync(cancellationToken);

        if (gradeSheet is null)
            return [];

        // Find students already enrolled in any makeup session for this teaching task
        var alreadyEnrolledIds = await db.MakeupExamEnrollments.AsNoTracking()
            .Where(x => x.MakeupExamSession!.TeachingTaskId == teachingTaskId &&
                        x.MakeupExamSession!.MakeupExamPlan!.Status != MakeupExamPlanStatus.Archived)
            .Select(x => x.StudentId)
            .Distinct()
            .ToListAsync(cancellationToken);

        var enrolledSet = alreadyEnrolledIds.ToHashSet();
        var results = new Dictionary<Guid, EligibleStudent>();

        // 1. Approved deferred exams (highest priority)
        var deferredStudents = await db.DeferredExams.AsNoTracking()
            .Where(x => x.TeachingTaskId == teachingTaskId &&
                        x.Status == ApprovalStatus.Approved)
            .WhereNotIn(enrolledSet, x => x.StudentId)
            .Select(x => new
            {
                x.StudentId,
                x.Student!.StudentNumber,
                x.Student.Name,
                ClassName = x.Student.AdministrativeClass!.Name,
                x.Id
            })
            .ToListAsync(cancellationToken);

        foreach (var s in deferredStudents)
        {
            if (!results.ContainsKey(s.StudentId))
            {
                results[s.StudentId] = new EligibleStudent(
                    s.StudentId, s.StudentNumber, s.Name, s.ClassName,
                    MakeupReason.DeferredApproved, null,
                    SourceDeferredExamId: s.Id);
            }
        }

        // 2. Absent students
        var absentStudents = await db.GradeRecords.AsNoTracking()
            .Where(x => x.GradeSheetId == gradeSheet.Id &&
                        x.ExamStatus == GradeExamStatus.Absent)
            .WhereNotIn(enrolledSet, x => x.StudentId)
            .Select(x => new
            {
                x.StudentId,
                x.Student!.StudentNumber,
                x.Student.Name,
                ClassName = x.Student.AdministrativeClass!.Name,
                GradeRecordId = (Guid?)x.Id
            })
            .ToListAsync(cancellationToken);

        foreach (var s in absentStudents)
        {
            if (!results.ContainsKey(s.StudentId))
            {
                results[s.StudentId] = new EligibleStudent(
                    s.StudentId, s.StudentNumber, s.Name, s.ClassName,
                    MakeupReason.Absent, null,
                    SourceGradeRecordId: s.GradeRecordId);
            }
        }

        // 3. Failed students (TotalScore < 60, Normal status)
        var failedStudents = await db.GradeRecords.AsNoTracking()
            .Where(x => x.GradeSheetId == gradeSheet.Id &&
                        x.ExamStatus == GradeExamStatus.Normal &&
                        x.TotalScore < 60)
            .WhereNotIn(enrolledSet, x => x.StudentId)
            .Select(x => new
            {
                x.StudentId,
                x.Student!.StudentNumber,
                x.Student.Name,
                ClassName = x.Student.AdministrativeClass!.Name,
                x.TotalScore,
                GradeRecordId = (Guid?)x.Id
            })
            .ToListAsync(cancellationToken);

        foreach (var s in failedStudents)
        {
            if (!results.ContainsKey(s.StudentId))
            {
                results[s.StudentId] = new EligibleStudent(
                    s.StudentId, s.StudentNumber, s.Name, s.ClassName,
                    MakeupReason.Failed, s.TotalScore,
                    SourceGradeRecordId: s.GradeRecordId);
            }
        }

        return [.. results.Values.OrderBy(x => x.StudentNumber)];
    }
}

public sealed record EligibleStudent(
    Guid StudentId,
    string StudentNumber,
    string Name,
    string ClassName,
    MakeupReason Reason,
    decimal? TotalScore = null,
    Guid? SourceGradeRecordId = null,
    Guid? SourceDeferredExamId = null,
    string? Detail = null);
