using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.System;
using Jiaowu.Api.Infrastructure.Grades;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;

namespace Jiaowu.Api.Tests;

public sealed class CourseGradeStatisticsRefreshSchedulerTests
{
    [Fact]
    public async Task EnqueueDueAsync_UsesPersistentScheduleAndHonorsInterval()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(connection)
            .Options;
        await using var db = new AppDbContext(options);
        await db.Database.EnsureCreatedAsync();
        var scheduler = new CourseGradeStatisticsRefreshScheduler(
            db,
            NullLogger<CourseGradeStatisticsRefreshScheduler>.Instance);
        var now = new DateTime(2026, 8, 9, 12, 0, 0, DateTimeKind.Utc);

        await scheduler.EnqueueDueAsync(now, CancellationToken.None);
        var setting = Assert.Single(await db.CourseGradeStatisticsRefreshSettings.ToListAsync());
        Assert.True(setting.IsEnabled);
        Assert.Equal(now, setting.LastRunAt);

        await scheduler.EnqueueDueAsync(now.AddMinutes(1), CancellationToken.None);
        Assert.Equal(now, setting.LastRunAt);
    }

    [Fact]
    public async Task EnqueueStaleAsync_GroupsByCourseTerm_AndSkipsActiveTarget()
    {
        await using var connection = new SqliteConnection("Data Source=:memory:");
        await connection.OpenAsync();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(connection)
            .Options;
        await using var db = new AppDbContext(options);
        await db.Database.EnsureCreatedAsync();

        var college = new College { Code = "SCHEDULE", Name = "定时任务学院" };
        var major = new Major
        {
            Code = "SCHEDULE-M",
            Name = "定时任务专业",
            College = college,
            DegreeType = "本科"
        };
        var administrativeClass = new AdministrativeClass
        {
            Code = "SCHEDULE-C",
            Name = "定时任务一班",
            Major = major,
            Grade = 2026
        };
        var student = new Student
        {
            StudentNumber = "SCHEDULE-001",
            Name = "定时任务学生",
            AdministrativeClass = administrativeClass,
            EnrollmentYear = 2026,
            EnrollmentDate = new DateOnly(2026, 9, 1)
        };
        var course = new Course
        {
            Code = "SCHEDULE-COURSE",
            Name = "定时任务课程",
            College = college,
            Credits = 2,
            TotalHours = 32,
            LectureHours = 32
        };
        var term = new AcademicTerm
        {
            Code = "2026-A",
            Name = "2026-2027 学年第一学期",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 1),
            EndDate = new DateOnly(2027, 1, 15)
        };
        var firstTask = new TeachingTask
        {
            TaskNumber = "SCHEDULE-T1",
            Name = "定时任务教学班一",
            Course = course,
            AcademicTerm = term,
            Capacity = 30
        };
        var secondTask = new TeachingTask
        {
            TaskNumber = "SCHEDULE-T2",
            Name = "定时任务教学班二",
            Course = course,
            AcademicTerm = term,
            Capacity = 30
        };
        var firstSheet = PublishedSheet(firstTask, student, 80m);
        var secondSheet = PublishedSheet(secondTask, student, 90m);
        db.GradeSheets.AddRange(firstSheet, secondSheet);
        await db.SaveChangesAsync();

        var scheduler = new CourseGradeStatisticsRefreshScheduler(
            db,
            NullLogger<CourseGradeStatisticsRefreshScheduler>.Instance);

        var queued = await scheduler.EnqueueStaleAsync(100, CancellationToken.None);
        var queuedAgain = await scheduler.EnqueueStaleAsync(100, CancellationToken.None);

        Assert.Equal(1, queued);
        Assert.Equal(0, queuedAgain);
        var job = Assert.Single(await db.CourseGradeStatisticsRefreshJobs.ToListAsync());
        var outbox = Assert.Single(await db.BackgroundJobOutboxMessages.ToListAsync());
        Assert.Equal(BackgroundJobKind.CourseGradeStatisticsRefresh, outbox.JobKind);

        job.Status = CourseGradeStatisticsRefreshJobStatus.Succeeded;
        db.TeachingTaskGradeStatistics.AddRange(
            FreshStatistic(firstSheet, course, term),
            FreshStatistic(secondSheet, course, term));
        await db.SaveChangesAsync();

        var queuedAfterFreshStatistics = await scheduler.EnqueueStaleAsync(
            100,
            CancellationToken.None);

        Assert.Equal(0, queuedAfterFreshStatistics);
        Assert.Single(await db.CourseGradeStatisticsRefreshJobs.ToListAsync());
    }

    private static GradeSheet PublishedSheet(
        TeachingTask task,
        Student student,
        decimal score)
    {
        var sheet = new GradeSheet
        {
            TeachingTask = task,
            Status = GradeSheetStatus.Published,
            PublishedAt = DateTime.UtcNow
        };
        sheet.Records.Add(new GradeRecord
        {
            GradeSheet = sheet,
            Student = student,
            TotalScore = score,
            GradePoint = 3m
        });
        return sheet;
    }

    private static TeachingTaskGradeStatistic FreshStatistic(
        GradeSheet sheet,
        Course course,
        AcademicTerm term) => new()
    {
        GradeSheetId = sheet.Id,
        TeachingTaskId = sheet.TeachingTaskId,
        CourseId = course.Id,
        AcademicTermId = term.Id,
        StudentCount = 1,
        PassedCount = 1,
        HighestScore = 80m,
        AverageScore = 80m,
        MedianScore = 80m,
        LowestScore = 80m,
        PassRate = 100m,
        CalculatedAt = DateTime.UtcNow.AddMinutes(1)
    };
}
