using Jiaowu.Api.Domain.Academic;

namespace Jiaowu.Api.Infrastructure.Scheduling;

public static class ScheduleConflictDetector
{
    public static string? FindConflict(IReadOnlyList<ScheduleEntry> entries)
    {
        for (var i = 0; i < entries.Count; i++)
        {
            for (var j = i + 1; j < entries.Count; j++)
            {
                var first = entries[i];
                var second = entries[j];
                if (!TimeOverlaps(first, second)) continue;
                var reason = ConflictReason(first, second);
                if (reason is not null)
                    return $"“{first.TeachingTask!.Name}”与“{second.TeachingTask!.Name}”存在{reason}冲突。";
            }
        }
        return null;
    }

    public static bool TimeOverlaps(ScheduleEntry first, ScheduleEntry second) =>
        first.DayOfWeek == second.DayOfWeek &&
        first.StartPeriod < second.StartPeriod + second.PeriodCount &&
        second.StartPeriod < first.StartPeriod + first.PeriodCount &&
        WeeksOverlap(first, second);

    public static string? ConflictReason(ScheduleEntry first, ScheduleEntry second)
    {
        if (first.ClassroomId.HasValue &&
            second.ClassroomId.HasValue &&
            first.ClassroomId == second.ClassroomId)
            return "教室";
        var firstTeachers = first.TeachingTask!.Teachers.Select(x => x.TeacherId).ToHashSet();
        if (second.TeachingTask!.Teachers.Any(x => firstTeachers.Contains(x.TeacherId)))
            return "教师";
        var firstClasses = first.TeachingTask.Classes
            .Select(x => x.AdministrativeClassId)
            .ToHashSet();
        return second.TeachingTask.Classes.Any(x =>
            firstClasses.Contains(x.AdministrativeClassId))
            ? "行政班"
            : null;
    }

    private static bool WeeksOverlap(ScheduleEntry first, ScheduleEntry second)
    {
        var start = Math.Max(first.StartWeek, second.StartWeek);
        var end = Math.Min(first.EndWeek, second.EndWeek);
        for (var week = start; week <= end; week++)
        {
            if (IncludesWeek(first.WeekPattern, week) &&
                IncludesWeek(second.WeekPattern, week))
                return true;
        }
        return false;
    }

    private static bool IncludesWeek(WeekPattern pattern, int week) =>
        pattern == WeekPattern.All ||
        pattern == WeekPattern.Odd && week % 2 == 1 ||
        pattern == WeekPattern.Even && week % 2 == 0;
}
