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

namespace Jiaowu.Api.Infrastructure.Scheduling;

public sealed class AutomaticScheduleGenerator(AppDbContext db)
{
    public Task<AutomaticScheduleResult> GenerateAsync(
        SchedulePlan plan,
        CancellationToken cancellationToken) =>
        GenerateAsync(plan, null, true, cancellationToken);

    public async Task<AutomaticScheduleResult> GenerateAsync(
        SchedulePlan plan,
        Func<AutomaticScheduleProgress, CancellationToken, Task>? reportProgress,
        bool saveChanges,
        CancellationToken cancellationToken)
    {
        var timeSlots = await db.ScheduleTimeSlots.AsNoTracking()
            .Where(x => x.AcademicTermId == plan.AcademicTermId && x.IsEnabled)
            .OrderBy(x => x.PeriodNumber)
            .ToListAsync(cancellationToken);
        if (timeSlots.Count == 0)
            return new(0, 0, ["请先维护该学期的上课时间表。"]);

        var activePeriods = timeSlots.Select(x => x.PeriodNumber).ToHashSet();
        var tasks = await db.TeachingTasks
             .Where(x =>
                 x.AcademicTermId == plan.AcademicTermId &&
                 x.Status == TeachingTaskStatus.Published &&
                 x.SchedulingMode == TeachingTaskSchedulingMode.Standard)
            .Include(x => x.Teachers)
            .Include(x => x.Classes)
            .ThenInclude(x => x.AdministrativeClass)
            .ThenInclude(x => x!.Students)
            .Include(x => x.Course)
            .OrderByDescending(x => x.Classes.Count + x.Teachers.Count)
            .ThenByDescending(x => x.Capacity)
            .ThenBy(x => x.TaskNumber)
            .ToListAsync(cancellationToken);
        var taskIds = tasks.Select(task => task.Id).ToArray();
        var constraints = await db.TeachingTaskScheduleConstraints.AsNoTracking()
            .WhereIn(taskIds, x => x.TeachingTaskId)
            .Include(x => x.AllowedClassrooms)
            .ToDictionaryAsync(x => x.TeachingTaskId, cancellationToken);
        var classrooms = await db.Classrooms.AsNoTracking()
            .Where(x => x.IsEnabled)
            .Include(x => x.Building)
            .OrderBy(x => x.Capacity)
            .ThenBy(x => x.Name)
            .ToListAsync(cancellationToken);
        var entries = await db.ScheduleEntries
            .Where(x => x.SchedulePlanId == plan.Id)
            .Include(x => x.TeachingTask)
            .ThenInclude(x => x!.Teachers)
            .Include(x => x.TeachingTask)
            .ThenInclude(x => x!.Classes)
            .ToListAsync(cancellationToken);

        var created = 0;
        var completedTasks = 0;
        var processedTasks = 0;
        var messages = new List<string>();
        if (reportProgress is not null)
        {
            await reportProgress(
                new(tasks.Count, 0, 0, 0),
                cancellationToken);
        }

        foreach (var task in tasks)
        {
            cancellationToken.ThrowIfCancellationRequested();
            constraints.TryGetValue(task.Id, out var constraint);
            if (!TeachingTaskHours.TryResolveRegularWeeklyHours(
                    task.Course!,
                    task.StartWeek,
                    task.EndWeek,
                    out var requiredWeeklyHours))
            {
                messages.Add(
                    $"{task.TaskNumber} · {task.Name} 的普通排课学时不能按授课周次整除，请调整教学任务周次。");
                processedTasks++;
                if (reportProgress is not null)
                {
                    await reportProgress(
                        new(tasks.Count, processedTasks, created, completedTasks),
                        cancellationToken);
                }
                continue;
            }

            var scheduledHours = entries
                .Where(x => x.TeachingTaskId == task.Id)
                .Sum(x => x.PeriodCount);
            if (scheduledHours > requiredWeeklyHours)
            {
                messages.Add(
                    $"{task.TaskNumber} · {task.Name} 已安排每周 {scheduledHours} 学时，" +
                    $"普通课表只需 {requiredWeeklyHours} 学时；请删除已包含的实践学时。");
                processedTasks++;
                if (reportProgress is not null)
                {
                    await reportProgress(
                        new(tasks.Count, processedTasks, created, completedTasks),
                        cancellationToken);
                }
                continue;
            }

            var remainingHours = requiredWeeklyHours - scheduledHours;
            if (remainingHours == 0)
            {
                completedTasks++;
                processedTasks++;
                if (reportProgress is not null)
                {
                    await reportProgress(
                        new(tasks.Count, processedTasks, created, completedTasks),
                        cancellationToken);
                }
                continue;
            }

            while (remainingHours > 0)
            {
                var desiredBlock = remainingHours >= 2 ? 2 : 1;
                var candidate = FindBestCandidate(
                    plan.Id,
                    task,
                    constraint,
                    desiredBlock,
                    activePeriods,
                    classrooms,
                    entries,
                    cancellationToken);
                if (candidate is null && desiredBlock > 1)
                {
                    candidate = FindBestCandidate(
                        plan.Id,
                        task,
                        constraint,
                        1,
                        activePeriods,
                        classrooms,
                        entries,
                        cancellationToken);
                }
                if (candidate is null) break;

                db.ScheduleEntries.Add(candidate);
                entries.Add(candidate);
                created++;
                remainingHours -= candidate.PeriodCount;
            }

            if (remainingHours == 0)
            {
                completedTasks++;
            }
            else
            {
                messages.Add(
                    $"{task.TaskNumber} · {task.Name} 仍有 {remainingHours} 学时无法安排，请检查教师/班级冲突或场地与时间约束。");
            }

            processedTasks++;
            if (reportProgress is not null)
            {
                await reportProgress(
                    new(tasks.Count, processedTasks, created, completedTasks),
                    cancellationToken);
            }
        }

        if (created > 0 && saveChanges)
            await db.SaveChangesAsync(cancellationToken);
        return new(
            created,
            completedTasks,
            messages,
            tasks.Count,
            processedTasks);
    }

    private static ScheduleEntry? FindBestCandidate(
        Guid planId,
        TeachingTask task,
        TeachingTaskScheduleConstraint? constraint,
        int periodCount,
        HashSet<int> activePeriods,
        IReadOnlyList<Classroom> classrooms,
        IReadOnlyList<ScheduleEntry> entries,
        CancellationToken cancellationToken)
    {
        var allowedDays = ParseAllowedDays(constraint?.AllowedDayOfWeeks);
        var firstPeriod = constraint?.EarliestPeriod ?? activePeriods.Min();
        var lastPeriod = constraint?.LatestPeriod ?? activePeriods.Max();
        var rooms = EligibleRooms(task, constraint, classrooms);
        if ((constraint?.RequiresClassroom ?? true) && rooms.Count == 0)
            return null;

        var candidates = new List<(ScheduleEntry Entry, int Score)>();
        foreach (var day in allowedDays)
        {
            cancellationToken.ThrowIfCancellationRequested();
            for (var start = firstPeriod; start + periodCount - 1 <= lastPeriod; start++)
            {
                cancellationToken.ThrowIfCancellationRequested();
                if (Enumerable.Range(start, periodCount).Any(period => !activePeriods.Contains(period)))
                    continue;

                var roomOptions = constraint?.RequiresClassroom == false
                    ? new Classroom?[] { null }
                    : rooms.Cast<Classroom?>().ToArray();
                foreach (var room in roomOptions)
                {
                    var proposed = new ScheduleEntry
                    {
                        SchedulePlanId = planId,
                        TeachingTaskId = task.Id,
                        TeachingTask = task,
                        ClassroomId = room?.Id,
                        DayOfWeek = day,
                        StartPeriod = start,
                        PeriodCount = periodCount,
                        StartWeek = task.StartWeek,
                        EndWeek = task.EndWeek,
                        WeekPattern = WeekPattern.All,
                        Notes = "自动排课"
                    };
                    if (entries.Any(existing =>
                            ScheduleConflictDetector.TimeOverlaps(existing, proposed) &&
                            ScheduleConflictDetector.ConflictReason(existing, proposed) is not null))
                        continue;

                    var sameTaskDay = entries.Count(x =>
                        x.TeachingTaskId == task.Id && x.DayOfWeek == day);
                    var dayLoad = entries.Count(x => x.DayOfWeek == day);
                    var roomWaste = room is null ? 0 : Math.Max(0, room.Capacity - task.Capacity);
                    var score = sameTaskDay * 1000 + dayLoad * 10 + start + roomWaste / 10;
                    candidates.Add((proposed, score));
                }
            }
        }
        return candidates
            .OrderBy(x => x.Score)
            .ThenBy(x => x.Entry.DayOfWeek)
            .ThenBy(x => x.Entry.StartPeriod)
            .Select(x => x.Entry)
            .FirstOrDefault();
    }

    private static IReadOnlyList<Classroom> EligibleRooms(
        TeachingTask task,
        TeachingTaskScheduleConstraint? constraint,
        IReadOnlyList<Classroom> classrooms)
    {
        if (constraint?.RequiresClassroom == false) return [];
        var allowedRoomIds = constraint?.AllowedClassrooms
            .Select(x => x.ClassroomId)
            .ToHashSet() ?? [];
        var minimumCapacity = Math.Max(
            task.Capacity,
            task.Classes.Sum(x =>
                x.AdministrativeClass?.Students.Count(student =>
                    student.Status == StudentStatus.Active) ?? 0));
        return classrooms.Where(room =>
                room.Capacity >= minimumCapacity &&
                (constraint?.RequiredCampusId is not Guid requiredCampusId ||
                    room.Building!.CampusId == requiredCampusId) &&
                (constraint?.RequiredBuildingId is not Guid requiredBuildingId ||
                    room.BuildingId == requiredBuildingId) &&
                (allowedRoomIds.Count == 0 || allowedRoomIds.Contains(room.Id)))
            .ToList();
    }

    private static int[] ParseAllowedDays(string? value)
    {
        if (string.IsNullOrWhiteSpace(value)) return [1, 2, 3, 4, 5];
        var days = value.Split(',', StringSplitOptions.RemoveEmptyEntries)
            .Select(item => int.TryParse(item, out var day) ? day : 0)
            .Where(day => day is >= 1 and <= 7)
            .Distinct()
            .Order()
            .ToArray();
        return days.Length == 0 ? [1, 2, 3, 4, 5] : days;
    }
}

public sealed record AutomaticScheduleResult(
    int CreatedEntries,
    int CompletedTasks,
    IReadOnlyList<string> Messages,
    int TotalTasks = 0,
    int ProcessedTasks = 0);

public sealed record AutomaticScheduleProgress(
    int TotalTasks,
    int ProcessedTasks,
    int CreatedEntries,
    int CompletedTasks);
