using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.Scheduling;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace Jiaowu.Api.Tests;

public sealed class SchedulePublishJobProcessorTests
{
    [Fact]
    public async Task Processor_validates_and_publishes_plan_atomically()
    {
        var result = await RunPublishAsync(weeklyHours: 2, scheduledHours: 2);

        Assert.Equal(SchedulePublishJobStatus.Succeeded, result.JobStatus);
        Assert.Equal(SchedulePlanStatus.Published, result.PlanStatus);
        Assert.Null(result.ActiveAcademicTermId);
        Assert.Equal(5, result.CompletedSteps);
        Assert.Equal("课表已发布", result.CurrentStep);
        Assert.Null(result.ErrorMessage);
        Assert.NotNull(result.PublishedAt);
    }

    [Fact]
    public async Task Processor_keeps_draft_when_validation_fails()
    {
        var result = await RunPublishAsync(weeklyHours: 2, scheduledHours: 1);

        Assert.Equal(SchedulePublishJobStatus.Failed, result.JobStatus);
        Assert.Equal(SchedulePlanStatus.Draft, result.PlanStatus);
        Assert.Null(result.ActiveAcademicTermId);
        Assert.Equal("检查未通过", result.CurrentStep);
        Assert.Contains("理论课应安排 32 学时，当前已安排 16 学时", result.ErrorMessage);
        Assert.Null(result.PublishedAt);
    }

    [Fact]
    public async Task Processor_requires_experiment_hours_in_regular_schedule()
    {
        var result = await RunPublishAsync(
            weeklyHours: 2,
            scheduledHours: 1,
            practiceHours: 16,
            experimentScheduledHours: 1);

        Assert.Equal(SchedulePublishJobStatus.Succeeded, result.JobStatus);
        Assert.Equal(SchedulePlanStatus.Published, result.PlanStatus);
    }

    [Fact]
    public async Task Processor_rejects_missing_experiment_hours()
    {
        var result = await RunPublishAsync(
            weeklyHours: 2,
            scheduledHours: 1,
            practiceHours: 16);

        Assert.Equal(SchedulePublishJobStatus.Failed, result.JobStatus);
        Assert.Equal(SchedulePlanStatus.Draft, result.PlanStatus);
        Assert.Contains("实验课应安排 16 学时，当前已安排 0 学时", result.ErrorMessage);
    }

    private static async Task<PublishResult> RunPublishAsync(
        int weeklyHours,
        int scheduledHours,
        int practiceHours = 0,
        int experimentScheduledHours = 0)
    {
        var databasePath = Path.Combine(
            Path.GetTempPath(),
            $"jiaowu-publish-{Guid.NewGuid():N}.sqlite");
        var services = new ServiceCollection();
        services.AddLogging();
        services.AddDbContext<AppDbContext>(
            options => options.UseSqlite(
                $"Data Source={databasePath};Pooling=False"));
        services.AddScoped<SchedulePlanPublisher>();
        services.AddScoped<SchedulePublishJobProcessor>();
        services.AddSingleton<IAppCache>(NoOpAppCache.Instance);
        var provider = services.BuildServiceProvider();

        try
        {
            Guid jobId;
            await using (var seedScope = provider.CreateAsyncScope())
            {
                var db = seedScope.ServiceProvider.GetRequiredService<AppDbContext>();
                await db.Database.EnsureCreatedAsync();
                var term = new AcademicTerm
                {
                    Code = $"2026-PUBLISH-{weeklyHours}",
                    Name = "后台发布学期",
                    AcademicYear = "2026-2027",
                    Season = TermSeason.Autumn,
                    StartDate = new DateOnly(2026, 9, 1),
                    EndDate = new DateOnly(2027, 1, 15)
                };
                var college = new College { Code = "PUB", Name = "发布测试学院" };
                var campus = new Campus { Code = "PUB-CAMPUS", Name = "发布测试校区" };
                var building = new Building
                {
                    Code = "PUB-BUILDING",
                    Name = "实验楼",
                    Campus = campus
                };
                var laboratory = new Classroom
                {
                    Code = "PUB-LAB",
                    Name = "发布测试实验室",
                    Building = building,
                    Capacity = 80,
                    RoomType = "实验室"
                };
                var course = new Course
                {
                    Code = "PUB-01",
                    Name = "发布测试课程",
                    College = college,
                    Credits = 1,
                    TotalHours = 32,
                    LectureHours = 32 - practiceHours,
                    PracticeHours = practiceHours
                };
                var task = new TeachingTask
                {
                    TaskNumber = $"TASK-PUB-{weeklyHours}",
                    Name = "发布测试教学班",
                    AcademicTerm = term,
                    Course = course,
                    Capacity = 60,
                    StartWeek = 1,
                    EndWeek = 16,
                    WeeklyHours = weeklyHours,
                    Status = TeachingTaskStatus.Published
                };
                var plan = new SchedulePlan
                {
                    AcademicTerm = term,
                    Name = "待发布课表",
                    Version = "V1"
                };
                plan.Entries.Add(new ScheduleEntry
                {
                    TeachingTask = task,
                    DayOfWeek = 1,
                    StartPeriod = 1,
                    PeriodCount = scheduledHours,
                    StartWeek = 1,
                    EndWeek = 16,
                    WeekPattern = WeekPattern.All
                });
                if (experimentScheduledHours > 0)
                {
                    plan.Entries.Add(new ScheduleEntry
                    {
                        TeachingTask = task,
                        Kind = ScheduleEntryKind.Experiment,
                        Classroom = laboratory,
                        DayOfWeek = 2,
                        StartPeriod = 1,
                        PeriodCount = experimentScheduledHours,
                        StartWeek = 1,
                        EndWeek = 16,
                        WeekPattern = WeekPattern.All
                    });
                }
                var job = new SchedulePublishJob
                {
                    SchedulePlan = plan,
                    AcademicTermId = term.Id,
                    ActiveAcademicTermId = term.Id,
                    CurrentStep = "等待后台检查"
                };
                jobId = job.Id;
                db.AddRange(
                    term,
                    college,
                    campus,
                    building,
                    laboratory,
                    course,
                    task,
                    plan,
                    job);
                db.ScheduleTimeSlots.AddRange(
                    new ScheduleTimeSlot
                    {
                        AcademicTerm = term,
                        PeriodNumber = 1,
                        Name = "第 1 节",
                        StartsAt = new TimeOnly(8, 0),
                        EndsAt = new TimeOnly(8, 45)
                    },
                    new ScheduleTimeSlot
                    {
                        AcademicTerm = term,
                        PeriodNumber = 2,
                        Name = "第 2 节",
                        StartsAt = new TimeOnly(8, 55),
                        EndsAt = new TimeOnly(9, 40)
                    });
                db.TeachingTaskScheduleConstraints.Add(
                    new TeachingTaskScheduleConstraint
                    {
                        TeachingTask = task,
                        RequiresClassroom = false
                    });
                await db.SaveChangesAsync();
            }

            await using (var workerScope = provider.CreateAsyncScope())
            {
                var processor = workerScope.ServiceProvider
                    .GetRequiredService<SchedulePublishJobProcessor>();
                await processor.ProcessAsync(jobId, CancellationToken.None);
            }

            await using var assertScope = provider.CreateAsyncScope();
            var assertDb = assertScope.ServiceProvider.GetRequiredService<AppDbContext>();
            var jobResult = await assertDb.SchedulePublishJobs.SingleAsync();
            var planResult = await assertDb.SchedulePlans.SingleAsync();
            return new(
                jobResult.Status,
                planResult.Status,
                jobResult.ActiveAcademicTermId,
                jobResult.CompletedSteps,
                jobResult.CurrentStep,
                jobResult.ErrorMessage,
                planResult.PublishedAt);
        }
        finally
        {
            await provider.DisposeAsync();
            File.Delete(databasePath);
        }
    }

    private sealed record PublishResult(
        SchedulePublishJobStatus JobStatus,
        SchedulePlanStatus PlanStatus,
        Guid? ActiveAcademicTermId,
        int CompletedSteps,
        string? CurrentStep,
        string? ErrorMessage,
        DateTime? PublishedAt);
}
