using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.CourseSelection;

namespace Jiaowu.Api.Tests;

public sealed class CourseSelectionRulesTests
{
    [Fact]
    public void Selection_requires_open_status_and_active_window()
    {
        var now = new DateTime(2026, 7, 24, 8, 0, 0, DateTimeKind.Utc);
        var round = CreateRound(now.AddHours(-1), now.AddHours(1));

        Assert.True(CourseSelectionRules.IsSelectionOpen(round, now));

        round.Status = CourseSelectionRoundStatus.Closed;
        Assert.False(CourseSelectionRules.IsSelectionOpen(round, now));

        round.Status = CourseSelectionRoundStatus.Open;
        Assert.False(CourseSelectionRules.IsSelectionOpen(round, now.AddHours(2)));
    }

    [Fact]
    public void Withdrawal_respects_deadline()
    {
        var now = new DateTime(2026, 7, 24, 8, 0, 0, DateTimeKind.Utc);
        var round = CreateRound(now.AddDays(-1), now.AddDays(1));
        round.WithdrawalEndsAt = now.AddMinutes(30);

        Assert.True(CourseSelectionRules.CanWithdraw(round, now));
        Assert.False(CourseSelectionRules.CanWithdraw(round, now.AddHours(1)));
    }

    [Fact]
    public void Waitlist_promotion_uses_withdrawal_window_and_retake_capacity()
    {
        var now = new DateTime(2026, 7, 24, 8, 0, 0, DateTimeKind.Utc);
        var round = CreateRound(now.AddHours(-1), now.AddHours(1));
        round.WithdrawalEndsAt = now.AddHours(2);

        Assert.True(CourseSelectionRules.CanPromoteWaitlist(round, now));
        Assert.Equal(60, CourseSelectionRules.EffectiveCapacity(60, false));
        Assert.Equal(69, CourseSelectionRules.EffectiveCapacity(60, true));

        round.Status = CourseSelectionRoundStatus.Closed;
        Assert.False(CourseSelectionRules.CanPromoteWaitlist(round, now));
    }

    [Fact]
    public void Schedule_conflict_detects_overlapping_weeks_and_periods()
    {
        var selected = CreateEntry(1, 1, 2, 1, 16, WeekPattern.All);
        var overlapping = CreateEntry(1, 2, 2, 1, 16, WeekPattern.All);

        Assert.True(CourseSelectionRules.HasScheduleConflict(
            [overlapping],
            [selected]));
    }

    [Fact]
    public void Odd_and_even_week_entries_do_not_conflict()
    {
        var selected = CreateEntry(3, 5, 2, 1, 16, WeekPattern.Odd);
        var candidate = CreateEntry(3, 5, 2, 1, 16, WeekPattern.Even);

        Assert.False(CourseSelectionRules.HasScheduleConflict(
            [candidate],
            [selected]));
    }

    [Theory]
    [InlineData(CourseNature.GeneralRequired, true)]
    [InlineData(CourseNature.GeneralElective, false)]
    [InlineData(CourseNature.MajorRequired, false)]
    [InlineData(CourseNature.MajorElective, false)]
    [InlineData(CourseNature.Practice, false)]
    public void Proxy_enrollment_is_limited_to_general_required_courses(
        CourseNature nature,
        bool expected)
    {
        Assert.Equal(expected, CourseSelectionRules.SupportsProxyEnrollment(nature));
    }

    [Fact]
    public void Empty_grade_scope_allows_all_students_and_configured_scope_is_enforced()
    {
        Assert.True(CourseSelectionRules.IsGradeEligible([], 2026));
        Assert.True(CourseSelectionRules.IsGradeEligible([2025, 2026], 2026));
        Assert.False(CourseSelectionRules.IsGradeEligible([2024, 2025], 2026));
    }

    [Theory]
    [InlineData(null, 99, false)]
    [InlineData(6, 5, false)]
    [InlineData(6, 6, true)]
    [InlineData(6, 7, true)]
    public void Course_count_limit_is_optional_and_blocks_at_the_boundary(
        int? maxCourseCount,
        int selectedCourseCount,
        bool expected)
    {
        Assert.Equal(
            expected,
            CourseSelectionRules.HasReachedCourseLimit(
                maxCourseCount,
                selectedCourseCount));
    }

    [Theory]
    [InlineData(TeachingTaskSchedulingMode.Standard, true)]
    [InlineData(TeachingTaskSchedulingMode.Flexible, false)]
    public void Only_standard_courses_require_a_published_schedule(
        TeachingTaskSchedulingMode schedulingMode,
        bool expected)
    {
        Assert.Equal(
            expected,
            CourseSelectionRules.RequiresPublishedSchedule(schedulingMode));
    }

    private static CourseSelectionRound CreateRound(DateTime startsAt, DateTime endsAt) =>
        new()
        {
            Name = "第一轮选课",
            StartsAt = startsAt,
            EndsAt = endsAt,
            WithdrawalEndsAt = endsAt.AddDays(1),
            Status = CourseSelectionRoundStatus.Open
        };

    private static ScheduleEntry CreateEntry(
        int dayOfWeek,
        int startPeriod,
        int periodCount,
        int startWeek,
        int endWeek,
        WeekPattern weekPattern) =>
        new()
        {
            DayOfWeek = dayOfWeek,
            StartPeriod = startPeriod,
            PeriodCount = periodCount,
            StartWeek = startWeek,
            EndWeek = endWeek,
            WeekPattern = weekPattern
        };
}
