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("尚未达到每周 2 个普通排课学时", result.ErrorMessage);
        Assert.Null(result.PublishedAt);
    }

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

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

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

        Assert.Equal(SchedulePublishJobStatus.Failed, result.JobStatus);
        Assert.Equal(SchedulePlanStatus.Draft, result.PlanStatus);
        Assert.Contains("普通课表应为 1 学时", result.ErrorMessage);
        Assert.Contains("删除已包含的实践学时", result.ErrorMessage);
    }

    private static async Task<PublishResult> RunPublishAsync(
        int weeklyHours,
        int scheduledHours,
        int practiceHours = 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 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
                });
                var job = new SchedulePublishJob
                {
                    SchedulePlan = plan,
                    AcademicTermId = term.Id,
                    ActiveAcademicTermId = term.Id,
                    CurrentStep = "等待后台检查"
                };
                jobId = job.Id;
                db.AddRange(term, college, 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);
}
