using System.ComponentModel.DataAnnotations;
using System.Data;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.Teaching;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Controllers;

[ApiController]
[Authorize(Roles = ManagementRoles)]
[Route("api/teaching-tasks")]
public sealed class TeachingTasksController(
    AppDbContext db,
    ICurrentUserDataScope currentUserDataScope,
    IAppCache cache) : ControllerBase
{
    private const string ManagementRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin + "," +
        SystemRoles.CollegeAdmin;
    private const string SchoolManagementRoles =
        SystemRoles.SuperAdmin + "," +
        SystemRoles.AcademicAdmin;

    [HttpGet]
    public async Task<ActionResult<PagedResult<object>>> Get(
        int page = 1,
        int pageSize = 20,
        string? keyword = null,
        Guid? academicTermId = null,
        Guid? collegeId = null,
        TeachingTaskStatus? status = null,
        CancellationToken cancellationToken = default)
    {
        page = Math.Max(1, page);
        pageSize = Math.Clamp(pageSize, 10, 100);
        var source = ScopedTasks().AsNoTracking();
        if (academicTermId.HasValue)
            source = source.Where(x => x.AcademicTermId == academicTermId);
        if (collegeId.HasValue)
            source = source.Where(x => x.Course!.CollegeId == collegeId);
        if (status.HasValue) source = source.Where(x => x.Status == status);
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            keyword = keyword.Trim();
            source = source.Where(x =>
                x.TaskNumber.Contains(keyword) ||
                x.Name.Contains(keyword) ||
                x.Course!.Code.Contains(keyword) ||
                x.Course.Name.Contains(keyword));
        }

        var total = await source.CountAsync(cancellationToken);
        var items = await source
            .OrderByDescending(x => x.AcademicTerm!.StartDate)
            .ThenBy(x => x.TaskNumber)
            .Skip((page - 1) * pageSize)
            .Take(pageSize)
            .Select(x => new
            {
                x.Id,
                x.TaskNumber,
                x.Name,
                x.AcademicTermId,
                TermName = x.AcademicTerm!.Name,
                x.CourseId,
                CourseCode = x.Course!.Code,
                CourseName = x.Course.Name,
                CourseNature = x.Course.Nature,
                CollegeId = x.Course.CollegeId,
                CollegeName = x.Course.College!.Name,
                x.Capacity,
                x.StartWeek,
                x.EndWeek,
                x.WeeklyHours,
                x.SchedulingMode,
                CourseTotalHours = x.Course.TotalHours,
                CoursePracticeHours = x.Course.PracticeHours,
                CourseRegularScheduleHours =
                    x.Course.TotalHours - x.Course.PracticeHours,
                x.GenerationBatchCode,
                x.Status,
                TeacherNames = x.Teachers
                    .OrderByDescending(item => item.IsPrimary)
                    .Select(item => item.Teacher!.Name),
                ClassNames = x.Classes.Select(item => item.AdministrativeClass!.Name),
                StudentCount = x.Classes.Sum(item =>
                    item.AdministrativeClass!.Students.Count(student =>
                        student.Status == StudentStatus.Active)),
                x.UpdatedAt
            })
            .ToListAsync(cancellationToken);
        return Ok(new PagedResult<object>(items, total, page, pageSize));
    }

    [HttpGet("options")]
    public async Task<ActionResult> GetOptions(
        Guid academicTermId,
        TeachingTaskStatus status = TeachingTaskStatus.Published,
        CancellationToken cancellationToken = default)
    {
        var items = await ScopedTasks()
            .AsNoTracking()
            .Where(x =>
                x.AcademicTermId == academicTermId &&
                x.Status == status)
            .OrderBy(x => x.Course!.Code)
            .ThenBy(x => x.TaskNumber)
            .Select(x => new
            {
                x.Id,
                x.TaskNumber,
                x.Name,
                x.AcademicTermId,
                x.CourseId,
                CourseCode = x.Course!.Code,
                CourseName = x.Course.Name,
                CourseNature = x.Course.Nature,
                CollegeId = x.Course.CollegeId,
                CollegeName = x.Course.College!.Name,
                x.Capacity,
                x.StartWeek,
                x.EndWeek,
                x.WeeklyHours,
                x.SchedulingMode,
                CourseTotalHours = x.Course!.TotalHours,
                CoursePracticeHours = x.Course.PracticeHours,
                CourseRegularScheduleHours =
                    x.Course.TotalHours - x.Course.PracticeHours,
                TeacherNames = x.Teachers
                    .OrderByDescending(item => item.IsPrimary)
                    .Select(item => item.Teacher!.Name),
                ClassNames = x.Classes
                    .OrderBy(item => item.AdministrativeClass!.Code)
                    .Select(item => item.AdministrativeClass!.Name)
            })
            .ToListAsync(cancellationToken);
        return Ok(items);
    }

    [HttpGet("{id:guid}")]
    public async Task<ActionResult> GetOne(Guid id, CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .AsNoTracking()
            .Where(x => x.Id == id)
            .Select(x => new
            {
                x.Id,
                x.TaskNumber,
                x.Name,
                x.AcademicTermId,
                TermName = x.AcademicTerm!.Name,
                x.CourseId,
                CourseCode = x.Course!.Code,
                CourseName = x.Course.Name,
                CourseNature = x.Course.Nature,
                CollegeId = x.Course.CollegeId,
                CollegeName = x.Course.College!.Name,
                x.Capacity,
                x.StartWeek,
                x.EndWeek,
                x.WeeklyHours,
                x.SchedulingMode,
                CourseTotalHours = x.Course.TotalHours,
                CoursePracticeHours = x.Course.PracticeHours,
                CourseRegularScheduleHours =
                    x.Course.TotalHours - x.Course.PracticeHours,
                x.GenerationBatchCode,
                x.Status,
                x.Notes,
                x.PublishedAt,
                TeacherIds = x.Teachers.Select(item => item.TeacherId),
                PrimaryTeacherId = x.Teachers
                    .Where(item => item.IsPrimary)
                    .Select(item => (Guid?)item.TeacherId)
                    .FirstOrDefault(),
                Teachers = x.Teachers
                    .OrderByDescending(item => item.IsPrimary)
                    .ThenBy(item => item.Teacher!.TeacherNumber)
                    .Select(item => new
                    {
                        item.TeacherId,
                        item.Teacher!.TeacherNumber,
                        item.Teacher.Name,
                        item.Teacher.Title,
                        item.IsPrimary
                    }),
                ClassIds = x.Classes.Select(item => item.AdministrativeClassId),
                Classes = x.Classes
                    .OrderBy(item => item.AdministrativeClass!.Code)
                    .Select(item => new
                    {
                        item.AdministrativeClassId,
                        item.AdministrativeClass!.Code,
                        item.AdministrativeClass.Name,
                        item.AdministrativeClass.Grade,
                        StudentCount = item.AdministrativeClass.Students.Count(student =>
                            student.Status == StudentStatus.Active)
                    }),
                x.CreatedAt,
                x.UpdatedAt
            })
            .FirstOrDefaultAsync(cancellationToken);
        return task is null ? NotFound() : Ok(task);
    }

    [HttpPost]
    public async Task<ActionResult> Create(
        TeachingTaskRequest request,
        CancellationToken cancellationToken)
    {
        var validation = await ValidateRequestAsync(request, cancellationToken);
        if (validation is not null) return validation;
        var task = new TeachingTask
        {
            TaskNumber = request.TaskNumber.Trim(),
            Name = request.Name.Trim(),
            AcademicTermId = request.AcademicTermId,
            CourseId = request.CourseId,
            Capacity = request.Capacity,
            StartWeek = request.StartWeek,
            EndWeek = request.EndWeek,
            WeeklyHours = request.WeeklyHours,
            SchedulingMode = request.SchedulingMode,
            Notes = Normalize(request.Notes)
        };
        SetAssignments(task, request);
        db.TeachingTasks.Add(task);
        return await SaveAsync(task.Id, true, cancellationToken);
    }

    [HttpPut("{id:guid}")]
    public async Task<ActionResult> Update(
        Guid id,
        TeachingTaskRequest request,
        CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .Include(x => x.Course)
            .Include(x => x.Teachers)
            .Include(x => x.Classes)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (task is null) return NotFound();
        if (!CanManage(task.Course!)) return Forbid();
        if (task.Status != TeachingTaskStatus.Draft)
            return ConflictProblem("已发布或已结课的教学任务不可直接修改。");
        var validation = await ValidateRequestAsync(request, cancellationToken);
        if (validation is not null) return validation;

        task.TaskNumber = request.TaskNumber.Trim();
        task.Name = request.Name.Trim();
        task.AcademicTermId = request.AcademicTermId;
        task.CourseId = request.CourseId;
        task.Capacity = request.Capacity;
        task.StartWeek = request.StartWeek;
        task.EndWeek = request.EndWeek;
        task.WeeklyHours = request.WeeklyHours;
        task.SchedulingMode = request.SchedulingMode;
        task.Notes = Normalize(request.Notes);
        db.TeachingTaskTeachers.RemoveRange(task.Teachers);
        db.TeachingTaskClasses.RemoveRange(task.Classes);
        task.Teachers = [];
        task.Classes = [];
        SetAssignments(task, request);
        return await SaveAsync(task.Id, false, cancellationToken);
    }

    [HttpDelete("{id:guid}")]
    public async Task<ActionResult> Delete(Guid id, CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .Include(x => x.Course)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (task is null) return NotFound();
        if (!CanManage(task.Course!)) return Forbid();
        if (task.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed))
            return ConflictProblem("仅草稿或已归档的教学任务可以删除。");
        db.TeachingTasks.Remove(task);
        return await SaveAsync(
            id,
            false,
            cancellationToken,
            "教学任务已被排课、选课、成绩或考试等业务引用，不能删除；如需继续使用，可撤销归档。");
    }

    [HttpPost("{id:guid}/publish")]
    public async Task<ActionResult> Publish(Guid id, CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .Include(x => x.Course)
            .Include(x => x.Teachers)
            .Include(x => x.Classes)
            .ThenInclude(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Students)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (task is null) return NotFound();
        if (!CanManage(task.Course!)) return Forbid();
        var validation = await ValidatePublishingTasksAsync([task], cancellationToken);
        if (validation is not null) return ConflictProblem(validation);

        task.Status = TeachingTaskStatus.Published;
        task.PublishedAt = DateTime.UtcNow;
        return await SaveAsync(id, false, cancellationToken);
    }

    [HttpPost("{id:guid}/close")]
    public async Task<ActionResult> Close(Guid id, CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .Include(x => x.Course)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (task is null) return NotFound();
        if (!CanManage(task.Course!)) return Forbid();
        if (task.Status != TeachingTaskStatus.Published)
            return ConflictProblem("只有已发布的教学任务可以归档。");
        task.Status = TeachingTaskStatus.Closed;
        return await SaveAsync(id, false, cancellationToken);
    }

    [HttpPost("{id:guid}/unarchive")]
    public async Task<ActionResult> Unarchive(Guid id, CancellationToken cancellationToken)
    {
        var task = await ScopedTasks()
            .Include(x => x.Course)
            .FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
        if (task is null) return NotFound();
        if (!CanManage(task.Course!)) return Forbid();
        if (task.Status != TeachingTaskStatus.Closed)
            return ConflictProblem("只有已归档的教学任务可以撤销归档。");
        task.Status = TeachingTaskStatus.Published;
        return await SaveAsync(id, false, cancellationToken);
    }

    [HttpPost("batch")]
    public async Task<ActionResult> Batch(
        TeachingTaskBatchRequest request,
        CancellationToken cancellationToken)
    {
        var ids = request.Ids.Distinct().ToArray();
        if (ids.Length == 0) return ValidationProblem("请至少选择一个教学任务。");
        if (ids.Length > 100) return ValidationProblem("每次最多处理 100 个教学任务。");

        var tasks = await ScopedTasks()
            .Include(x => x.Course)
            .Include(x => x.Teachers)
            .Include(x => x.Classes)
            .ThenInclude(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Students)
            .WhereIn(ids, x => x.Id)
            .OrderBy(x => x.TaskNumber)
            .ToListAsync(cancellationToken);
        if (tasks.Count != ids.Length)
            return ConflictProblem("部分教学任务不存在，或已超出当前账号的数据范围。");
        if (tasks.Any(x => !CanManage(x.Course!))) return Forbid();

        switch (request.Operation)
        {
            case TeachingTaskBatchOperation.Publish:
                {
                    var validation = await ValidatePublishingTasksAsync(tasks, cancellationToken);
                    if (validation is not null) return ConflictProblem(validation);
                    var publishedAt = DateTime.UtcNow;
                    foreach (var task in tasks)
                    {
                        task.Status = TeachingTaskStatus.Published;
                        task.PublishedAt = publishedAt;
                    }
                    break;
                }
            case TeachingTaskBatchOperation.Delete:
                if (tasks.Any(x =>
                        x.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed)))
                    return ConflictProblem(
                        $"教学任务“{tasks.First(x => x.Status is not (TeachingTaskStatus.Draft or TeachingTaskStatus.Closed)).Name}”不是草稿或已归档状态，整批未删除。");
                db.TeachingTasks.RemoveRange(tasks);
                break;
            case TeachingTaskBatchOperation.Archive:
                if (tasks.Any(x => x.Status != TeachingTaskStatus.Published))
                    return ConflictProblem(
                        $"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Published).Name}”不是已发布状态，整批未归档。");
                foreach (var task in tasks) task.Status = TeachingTaskStatus.Closed;
                break;
            case TeachingTaskBatchOperation.Unarchive:
                if (tasks.Any(x => x.Status != TeachingTaskStatus.Closed))
                    return ConflictProblem(
                        $"教学任务“{tasks.First(x => x.Status != TeachingTaskStatus.Closed).Name}”不是已归档状态，整批未撤销归档。");
                foreach (var task in tasks) task.Status = TeachingTaskStatus.Published;
                break;
            default:
                return ValidationProblem("不支持的批量操作。");
        }

        try
        {
            await db.SaveChangesAsync(cancellationToken);
            await cache.RemoveByTagAsync(AppCacheTags.Timetables, cancellationToken);
            return Ok(new { AffectedCount = tasks.Count });
        }
        catch (DbUpdateException)
        {
            return ConflictProblem("批量操作未执行：教学任务仍被其他业务引用，或关联数据已失效。");
        }
    }

    [HttpPost("generate-public-course")]
    [Authorize(Roles = SchoolManagementRoles)]
    public async Task<ActionResult> GeneratePublicCourseTasks(
        PublicCourseTaskGenerationRequest request,
        CancellationToken cancellationToken)
    {
        if (request.StartWeek > request.EndWeek)
            return ValidationProblem("开始周不能晚于结束周。");
        var term = await db.AcademicTerms.AsNoTracking()
            .FirstOrDefaultAsync(
                x => x.Id == request.AcademicTermId && x.IsEnabled,
                cancellationToken);
        if (term is null) return ValidationProblem("所选学期不存在或已停用。");
        var course = await db.Courses.AsNoTracking()
            .FirstOrDefaultAsync(
                x => x.Id == request.CourseId && x.IsEnabled,
                cancellationToken);
        if (course is null) return ValidationProblem("所选课程不存在或已停用。");
        if (!CanManage(course)) return Forbid();
        var hoursProblem = TeachingTaskHours.Validate(
            course,
            request.StartWeek,
            request.EndWeek,
            request.WeeklyHours,
            TeachingTaskSchedulingMode.Standard);
        if (hoursProblem is not null) return ValidationProblem(hoursProblem);
        if (course.Nature is not (CourseNature.GeneralRequired or CourseNature.GeneralElective))
            return ValidationProblem("批量合班生成仅用于公共必修课或公共选修课。");
        var scopedCollegeId = ScopedCollegeId();
        if (scopedCollegeId.HasValue && course.CollegeId != scopedCollegeId.Value)
            return Forbid();

        var classIds = request.ClassIds.Distinct().ToArray();
        if (classIds.Length == 0) return ValidationProblem("请至少选择一个行政班。");
        var classesQuery = db.AdministrativeClasses
            .Where(x => x.IsEnabled)
            .WhereIn(classIds, x => x.Id)
            .Include(x => x.Major)
            .Include(x => x.Students)
            .AsQueryable();
        if (scopedCollegeId.HasValue)
            classesQuery = classesQuery.Where(x => x.Major!.CollegeId == scopedCollegeId.Value);
        var classes = await classesQuery
            .OrderBy(x => x.Code)
            .ToListAsync(cancellationToken);
        if (classes.Count != classIds.Length)
            return ValidationProblem("存在无效或不在当前数据范围内的行政班。");

        return await db.ExecuteInRetriableTransactionAsync<ActionResult>(
            async transaction =>
            {
                db.ChangeTracker.Clear();
                var assignedClassIds = await db.TeachingTaskClasses.AsNoTracking()
                    .Where(x =>
                        x.TeachingTask!.AcademicTermId == request.AcademicTermId &&
                        x.TeachingTask.CourseId == request.CourseId)
                    .WhereIn(classIds, x => x.AdministrativeClassId)
                    .Select(x => x.AdministrativeClassId)
                    .Distinct()
                    .ToListAsync(cancellationToken);
                if (assignedClassIds.Count > 0)
                {
                    var names = classes
                        .Where(x => assignedClassIds.Contains(x.Id))
                        .Select(x => x.Name);
                    return ConflictProblem(
                        $"以下行政班已生成该课程教学任务：{string.Join('、', names)}。");
                }

                var eligibleTeachers = await db.TeacherCourseApplications.AsNoTracking()
                    .Where(x =>
                        x.AcademicTermId == request.AcademicTermId &&
                        x.CourseId == request.CourseId &&
                        x.Status == TeacherCourseApplicationStatus.Approved &&
                        x.Teacher!.Status == TeacherStatus.Active)
                    .Where(x => !scopedCollegeId.HasValue ||
                        x.Teacher!.CollegeId == scopedCollegeId.Value)
                    .Select(x => x.Teacher!)
                    .ToListAsync(cancellationToken);
                if (eligibleTeachers.Count == 0)
                    return ConflictProblem("该课程没有学院审核通过的可授课教师，无法生成教学任务。");

                var eligibleTeacherIds = eligibleTeachers.Select(x => x.Id).ToArray();
                var existingLoads = await db.TeachingTaskTeachers.AsNoTracking()
                    .Where(x =>
                        x.TeachingTask!.AcademicTermId == request.AcademicTermId &&
                        x.TeachingTask.Status != TeachingTaskStatus.Closed)
                    .WhereIn(eligibleTeacherIds, x => x.TeacherId)
                    .GroupBy(x => x.TeacherId)
                    .Select(group => new { TeacherId = group.Key, Count = group.Count() })
                    .ToDictionaryAsync(x => x.TeacherId, x => x.Count, cancellationToken);
                var existingNumbers = await db.TeachingTasks.AsNoTracking()
                    .Where(x => x.AcademicTermId == request.AcademicTermId)
                    .Select(x => x.TaskNumber)
                    .ToHashSetAsync(cancellationToken);
                var batchCode =
                    $"AUTO-{DateTime.UtcNow:yyyyMMddHHmmss}-{Guid.NewGuid():N}"[..31];
                var groups = classes.Chunk(request.ClassesPerTask).ToList();
                var teacherAssignments = PublicCourseTaskAssignmentPlanner.AssignTeachers(
                    eligibleTeacherIds,
                    existingLoads,
                    groups.Count);
                var created = new List<TeachingTask>();
                for (var index = 0; index < groups.Count; index++)
                {
                    var teacher = eligibleTeachers.First(x => x.Id == teacherAssignments[index]);
                    var group = groups[index];
                    var taskNumber = NextTaskNumber(
                        term.Code,
                        course.Code,
                        index + 1,
                        existingNumbers);
                    existingNumbers.Add(taskNumber);
                    var studentCount = group.Sum(administrativeClass =>
                        administrativeClass.Students.Count(student =>
                            student.Status == StudentStatus.Active));
                    var task = new TeachingTask
                    {
                        TaskNumber = taskNumber,
                        Name = $"{course.Name}教学班 {index + 1:D2}",
                        AcademicTermId = term.Id,
                        CourseId = course.Id,
                        Capacity = Math.Max(1, studentCount),
                        StartWeek = request.StartWeek,
                        EndWeek = request.EndWeek,
                        WeeklyHours = request.WeeklyHours,
                        GenerationBatchCode = batchCode,
                        Notes = "公共课合班自动生成，发布前可继续调整。",
                        Teachers =
                        [
                            new TeachingTaskTeacher
                    {
                        TeacherId = teacher.Id,
                        IsPrimary = true
                    }
                        ],
                        Classes = group.Select(administrativeClass =>
                            new TeachingTaskClass
                            {
                                AdministrativeClassId = administrativeClass.Id
                            }).ToList()
                    };
                    created.Add(task);
                }

                db.TeachingTasks.AddRange(created);
                await db.SaveChangesAsync(cancellationToken);
                await transaction.CommitAsync(cancellationToken);
                await cache.RemoveByTagAsync(
                    AppCacheTags.Timetables,
                    cancellationToken);
                return Ok(new
                {
                    BatchCode = batchCode,
                    CreatedCount = created.Count,
                    Tasks = created.Select(task => new
                    {
                        task.Id,
                        task.TaskNumber,
                        task.Name,
                        TeacherName = eligibleTeachers
                            .First(x => x.Id == task.Teachers.Single().TeacherId).Name,
                        ClassNames = classes
                            .Where(x => task.Classes.Any(item =>
                                item.AdministrativeClassId == x.Id))
                            .Select(x => x.Name),
                        task.Capacity
                    })
                });
            },
            cancellationToken,
            IsolationLevel.Serializable);
    }

    private IQueryable<TeachingTask> ScopedTasks()
    {
        var source = db.TeachingTasks.AsQueryable();
        var collegeId = ScopedCollegeId();
        return collegeId.HasValue
            ? source.Where(x => x.Course!.CollegeId == collegeId.Value)
            : source;
    }

    private async Task<ActionResult?> ValidateRequestAsync(
        TeachingTaskRequest request,
        CancellationToken cancellationToken)
    {
        if (request.StartWeek > request.EndWeek)
            return ValidationProblem("开始周不能晚于结束周。");
        var course = await db.Courses.AsNoTracking()
            .FirstOrDefaultAsync(x => x.Id == request.CourseId && x.IsEnabled, cancellationToken);
        if (course is null) return ValidationProblem("所选课程不存在或已停用。");
        if (!CanManage(course)) return Forbid();
        if (!Enum.IsDefined(request.SchedulingMode))
            return ValidationProblem("授课方式无效。");
        var hoursProblem = TeachingTaskHours.Validate(
            course,
            request.StartWeek,
            request.EndWeek,
            request.WeeklyHours,
            request.SchedulingMode);
        if (hoursProblem is not null) return ValidationProblem(hoursProblem);
        var collegeId = ScopedCollegeId();
        if (!await db.AcademicTerms.AnyAsync(
                x => x.Id == request.AcademicTermId && x.IsEnabled,
                cancellationToken))
            return ValidationProblem("所选学期不存在或已停用。");

        var teacherIds = request.TeacherIds.Distinct().ToArray();
        if (request.PrimaryTeacherId.HasValue &&
            !teacherIds.Contains(request.PrimaryTeacherId.Value))
            return ValidationProblem("主讲教师必须包含在授课教师中。");
        if (await db.Teachers
                .Where(x => x.Status == TeacherStatus.Active)
                .WhereIn(teacherIds, x => x.Id)
                .CountAsync(cancellationToken) != teacherIds.Length)
            return ValidationProblem("存在无效或非在职授课教师。");
        if (teacherIds.Length > 0 &&
            await db.TeacherCourseApplications
                .Where(x =>
                    x.AcademicTermId == request.AcademicTermId &&
                    x.CourseId == request.CourseId &&
                    x.Status == TeacherCourseApplicationStatus.Approved)
                .WhereIn(teacherIds, x => x.TeacherId)
                .CountAsync(cancellationToken) != teacherIds.Length)
            return ValidationProblem("授课教师必须已完成该课程申报并经学院审核通过。");

        var classIds = request.ClassIds.Distinct().ToArray();
        if (classIds.Length > 0)
        {
            var classes = db.AdministrativeClasses.AsNoTracking()
                .WhereIn(classIds, x => x.Id);
            if (collegeId.HasValue)
                classes = classes.Where(x => x.Major!.CollegeId == collegeId.Value);
            if (await classes.CountAsync(cancellationToken) != classIds.Length)
                return ValidationProblem("存在无效或不在当前数据范围内的行政班。");
        }
        return null;
    }

    private static void SetAssignments(TeachingTask task, TeachingTaskRequest request)
    {
        foreach (var teacherId in request.TeacherIds.Distinct())
        {
            task.Teachers.Add(new TeachingTaskTeacher
            {
                TeacherId = teacherId,
                IsPrimary = teacherId == request.PrimaryTeacherId
            });
        }
        foreach (var classId in request.ClassIds.Distinct())
        {
            task.Classes.Add(new TeachingTaskClass
            {
                AdministrativeClassId = classId
            });
        }
    }

    private async Task<string?> ValidatePublishingTasksAsync(
        IReadOnlyCollection<TeachingTask> tasks,
        CancellationToken cancellationToken)
    {
        var invalidStatus = tasks.FirstOrDefault(x => x.Status != TeachingTaskStatus.Draft);
        if (invalidStatus is not null)
            return PublishingProblem(
                tasks,
                invalidStatus,
                "只有草稿教学任务可以发布。");

        var invalidPrimaryTeacher = tasks.FirstOrDefault(x =>
            x.Teachers.Count == 0 || x.Teachers.Count(item => item.IsPrimary) != 1);
        if (invalidPrimaryTeacher is not null)
            return PublishingProblem(
                tasks,
                invalidPrimaryTeacher,
                "发布前必须指定且只能指定一名主讲教师。");

        var termIds = tasks.Select(x => x.AcademicTermId).Distinct().ToArray();
        var courseIds = tasks.Select(x => x.CourseId).Distinct().ToArray();
        var teacherIds = tasks
            .SelectMany(x => x.Teachers)
            .Select(x => x.TeacherId)
            .Distinct()
            .ToArray();
        var approvedApplications = await db.TeacherCourseApplications
            .AsNoTracking()
            .Where(x => x.Status == TeacherCourseApplicationStatus.Approved)
            .WhereIn(termIds, x => x.AcademicTermId)
            .WhereIn(courseIds, x => x.CourseId)
            .WhereIn(teacherIds, x => x.TeacherId)
            .Select(x => new { x.AcademicTermId, x.CourseId, x.TeacherId })
            .ToListAsync(cancellationToken);
        var approvedAssignments = approvedApplications
            .Select(x => (x.AcademicTermId, x.CourseId, x.TeacherId))
            .ToHashSet();
        var unapprovedTeacher = tasks.FirstOrDefault(task =>
            task.Teachers.Any(teacher =>
                !approvedAssignments.Contains(
                    (task.AcademicTermId, task.CourseId, teacher.TeacherId))));
        if (unapprovedTeacher is not null)
            return PublishingProblem(
                tasks,
                unapprovedTeacher,
                "授课教师尚未完成该课程申报，或学院审核尚未通过。");

        foreach (var task in tasks)
        {
            var studentCount = task.Classes.Sum(x =>
                x.AdministrativeClass!.Students.Count(student =>
                    student.Status == StudentStatus.Active));
            if (studentCount > task.Capacity)
                return PublishingProblem(
                    tasks,
                    task,
                    $"教学班容量不足：关联班级共有 {studentCount} 名在籍学生。");
        }

        return null;
    }

    private static string PublishingProblem(
        IReadOnlyCollection<TeachingTask> tasks,
        TeachingTask task,
        string detail) =>
        tasks.Count == 1 ? detail : $"教学任务“{task.Name}”：{detail}整批未发布。";

    private Guid? ScopedCollegeId()
        => currentUserDataScope.Current.RestrictedCollegeId;

    private bool CanManage(Course course) =>
        TeachingTaskMaintenancePolicy.CanManage(
            currentUserDataScope.Current,
            course.CollegeId,
            course.Nature);

    private async Task<ActionResult> SaveAsync(
        Guid id,
        bool created,
        CancellationToken cancellationToken,
        string conflictMessage = "教学任务编号重复，或关联数据已失效。")
    {
        try
        {
            await db.SaveChangesAsync(cancellationToken);
            await cache.RemoveByTagAsync(AppCacheTags.Timetables, cancellationToken);
            return created ? Created(string.Empty, new { id }) : NoContent();
        }
        catch (DbUpdateException)
        {
            return ConflictProblem(conflictMessage);
        }
    }

    private ActionResult ConflictProblem(string detail) =>
        Conflict(new ProblemDetails
        {
            Title = "无法完成操作",
            Detail = detail,
            Status = StatusCodes.Status409Conflict
        });

    private static string? Normalize(string? value) =>
        string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    private static string NextTaskNumber(
        string termCode,
        string courseCode,
        int sequence,
        IReadOnlySet<string> existing)
    {
        var prefix = $"{termCode}-{courseCode}"
            .Replace(" ", string.Empty, StringComparison.Ordinal);
        if (prefix.Length > 31) prefix = prefix[..31];
        var candidateSequence = sequence;
        while (true)
        {
            var candidate = $"{prefix}-A{candidateSequence:D2}";
            if (!existing.Contains(candidate)) return candidate;
            candidateSequence++;
        }
    }
}

public sealed record TeachingTaskRequest(
    [Required, MaxLength(40)] string TaskNumber,
    [Required, MaxLength(120)] string Name,
    Guid AcademicTermId,
    Guid CourseId,
    [Range(1, 10000)] int Capacity,
    [Range(1, 30)] int StartWeek,
    [Range(1, 30)] int EndWeek,
     [Range(1, 40)] int WeeklyHours,
     IReadOnlyCollection<Guid> TeacherIds,
     Guid? PrimaryTeacherId,
     IReadOnlyCollection<Guid> ClassIds,
     [MaxLength(500)] string? Notes,
     TeachingTaskSchedulingMode SchedulingMode = TeachingTaskSchedulingMode.Standard);

public sealed record PublicCourseTaskGenerationRequest(
    Guid AcademicTermId,
    Guid CourseId,
    [Range(1, 10)] int ClassesPerTask,
    [Range(1, 30)] int StartWeek,
    [Range(1, 30)] int EndWeek,
    [Range(1, 40)] int WeeklyHours,
    IReadOnlyCollection<Guid> ClassIds);

public sealed record TeachingTaskBatchRequest(
    TeachingTaskBatchOperation Operation,
    [MinLength(1), MaxLength(100)] IReadOnlyCollection<Guid> Ids);

public enum TeachingTaskBatchOperation
{
    Publish = 1,
    Delete = 2,
    Archive = 3,
    Unarchive = 4
}
