using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Graduation;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize(Roles = SystemRoles.Student)]
[Route("api/student/curriculum-plan")]
public sealed class StudentCurriculumController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope) : ControllerBase
{
    [HttpGet]
    public async Task<ActionResult> GetMine(CancellationToken cancellationToken)
    {
        var userId = currentUserDataScope.Current.UserId;
        var student = await db.Students.AsNoTracking()
            .Include(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Major)
            .ThenInclude(x => x!.College)
            .FirstOrDefaultAsync(x => x.UserId == userId, cancellationToken);
        if (student is null)
            return ConflictProblem("当前账号尚未关联学生档案。");

        var studentProfile = new
        {
            student.Id,
            student.StudentNumber,
            student.Name,
            student.EnrollmentYear,
            student.Status,
            ClassName = student.AdministrativeClass!.Name,
            MajorName = student.AdministrativeClass.Major!.Name,
            CollegeName = student.AdministrativeClass.Major.College!.Name
        };

        var plan = await db.CurriculumPlans.AsNoTracking()
            .AsSplitQuery()
            .Include(x => x.Major)
            .ThenInclude(x => x!.College)
            .Include(x => x.Modules)
            .ThenInclude(x => x.Courses)
            .ThenInclude(x => x.Course)
            .ThenInclude(x => x!.CourseCategory)
            .Where(x =>
                x.MajorId == student.AdministrativeClass.MajorId &&
                x.EffectiveGrade == student.EnrollmentYear &&
                x.Status == CurriculumPlanStatus.Published)
            .OrderByDescending(x => x.PublishedAt)
            .FirstOrDefaultAsync(cancellationToken);

        if (plan is null)
        {
            return Ok(new
            {
                Student = studentProfile,
                Plan = (object?)null,
                Message =
                    $"{student.EnrollmentYear} 级{student.AdministrativeClass.Major.Name}尚未发布培养方案。"
            });
        }

        var planCourseIds = plan.Modules
            .SelectMany(x => x.Courses)
            .Select(x => x.CourseId)
            .Distinct()
            .ToArray();
        var gradeAttempts = await db.GradeRecords.AsNoTracking()
            .Where(x =>
                x.StudentId == student.Id &&
                x.GradeSheet!.Status == GradeSheetStatus.Published)
            .WhereIn(planCourseIds, x => x.GradeSheet!.TeachingTask!.CourseId)
            .Select(x => new StudentGradeAttempt(
                x.GradeSheet!.TeachingTask!.CourseId,
                x.TotalScore,
                x.GradePoint,
                x.ExamStatus,
                x.GradeSheet.TeachingTask.AcademicTerm!.Name,
                x.GradeSheet.TeachingTask.TaskNumber,
                x.GradeSheet.PublishedAt ?? x.GradeSheet.UpdatedAt))
            .ToListAsync(cancellationToken);

        var currentCourseIds = await db.TeachingTasks.AsNoTracking()
            .Where(task =>
                task.Status == TeachingTaskStatus.Published &&
                task.AcademicTerm!.IsCurrent &&
                (task.Classes.Any(item =>
                     item.AdministrativeClassId == student.AdministrativeClassId) ||
                 db.CourseEnrollments.Any(enrollment =>
                     enrollment.StudentId == student.Id &&
                      enrollment.Status == CourseEnrollmentStatus.Enrolled &&
                      enrollment.CourseSelectionOffering!.TeachingTaskId == task.Id)))
            .WhereIn(planCourseIds, task => task.CourseId)
            .Select(x => x.CourseId)
            .Distinct()
            .ToListAsync(cancellationToken);
        var currentCourseIdSet = currentCourseIds.ToHashSet();
        var attemptsByCourse = gradeAttempts
            .GroupBy(x => x.CourseId)
            .ToDictionary(x => x.Key, x => x.OrderByDescending(
                attempt => attempt.PublishedAt).ToList());

        var progressByCourse = new Dictionary<Guid, CourseProgress>();
        foreach (var courseId in planCourseIds)
        {
            var attempts = attemptsByCourse.GetValueOrDefault(courseId) ?? [];
            var evaluation = StudentCourseProgressRules.Evaluate(
                attempts.Select(x => new StudentCourseAttemptSnapshot(
                    x.TotalScore,
                    x.ExamStatus)),
                currentCourseIdSet.Contains(courseId));
            var displayedAttempt = evaluation.HasPassedAttempt
                ? attempts.FirstOrDefault(x => StudentCourseProgressRules.IsPassed(
                    new StudentCourseAttemptSnapshot(x.TotalScore, x.ExamStatus)))
                : attempts.FirstOrDefault();
            progressByCourse[courseId] = new CourseProgress(
                evaluation,
                displayedAttempt);
        }

        var distinctCourses = plan.Modules
            .SelectMany(x => x.Courses)
            .GroupBy(x => x.CourseId)
            .Select(x => x.First())
            .ToList();
        var completedCredits = distinctCourses
            .Where(x =>
                progressByCourse[x.CourseId].Evaluation.Status ==
                StudentCourseProgressStatus.Completed)
            .Sum(x => x.Course?.Credits ?? 0);
        var completedCourseCount = distinctCourses.Count(x =>
            progressByCourse[x.CourseId].Evaluation.Status ==
            StudentCourseProgressStatus.Completed);
        var inProgressCourseCount = distinctCourses.Count(x =>
            progressByCourse[x.CourseId].Evaluation.Status is
                StudentCourseProgressStatus.InProgress or
                StudentCourseProgressStatus.Retaking);
        var failedCourseCount = distinctCourses.Count(x =>
            progressByCourse[x.CourseId].Evaluation.HasFailedAttempt &&
            !progressByCourse[x.CourseId].Evaluation.HasPassedAttempt);

        return Ok(new
        {
            Student = studentProfile,
            Plan = new
            {
                plan.Id,
                plan.Name,
                plan.Version,
                plan.EffectiveGrade,
                plan.TotalCredits,
                plan.Description,
                plan.PublishedAt,
                MajorName = plan.Major!.Name,
                CollegeName = plan.Major.College!.Name,
                SchoolingYears = plan.Major.SchoolingYears,
                Summary = new
                {
                    CourseCount = distinctCourses.Count,
                    CompletedCourseCount = completedCourseCount,
                    InProgressCourseCount = inProgressCourseCount,
                    FailedCourseCount = failedCourseCount,
                    CompletedCredits = completedCredits,
                    CompletionRate = plan.TotalCredits <= 0
                        ? 0
                        : Math.Min(100, Math.Round(
                            completedCredits / plan.TotalCredits * 100,
                            1))
                },
                Modules = plan.Modules
                    .OrderBy(x => x.SortOrder)
                    .ThenBy(x => x.Code)
                    .Select(module => new
                    {
                        module.Id,
                        module.Code,
                        module.Name,
                        module.RequiredCredits,
                        module.SortOrder,
                        Courses = module.Courses
                            .OrderBy(x => x.RecommendedSemester)
                            .ThenBy(x => x.Course!.Code)
                            .Select(item =>
                            {
                                var progress = progressByCourse[item.CourseId];
                                return new
                                {
                                    item.Id,
                                    item.CourseId,
                                    CourseCode = item.Course!.Code,
                                    CourseName = item.Course.Name,
                                    item.Course.Credits,
                                    item.Course.TotalHours,
                                    item.Course.Nature,
                                    CategoryName = item.Course.CourseCategory?.Name,
                                    item.RecommendedSemester,
                                    item.Type,
                                    item.Notes,
                                    progress.Evaluation.Status,
                                    progress.Evaluation.HasFailedAttempt,
                                    progress.Evaluation.HasPassedAttempt,
                                    LatestResult = progress.DisplayedAttempt is null
                                        ? null
                                        : new
                                        {
                                            progress.DisplayedAttempt.TotalScore,
                                            progress.DisplayedAttempt.GradePoint,
                                            progress.DisplayedAttempt.ExamStatus,
                                            progress.DisplayedAttempt.TermName,
                                            progress.DisplayedAttempt.TaskNumber,
                                            progress.DisplayedAttempt.PublishedAt
                                        }
                                };
                            })
                    })
            }
        });
    }

    private ActionResult ConflictProblem(string detail) =>
        Conflict(new ProblemDetails
        {
            Title = "无法查看培养方案",
            Detail = detail,
            Status = StatusCodes.Status409Conflict
        });

    private sealed record StudentGradeAttempt(
        Guid CourseId,
        decimal? TotalScore,
        decimal? GradePoint,
        GradeExamStatus ExamStatus,
        string TermName,
        string TaskNumber,
        DateTime PublishedAt);

    private sealed record CourseProgress(
        StudentCourseProgressEvaluation Evaluation,
        StudentGradeAttempt? DisplayedAttempt);
}
