using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Domain.System;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Exams;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;

namespace Jiaowu.Api.Tests;

public sealed class ExamArrangementJobControllerTests
{
    [Fact]
    public async Task Formal_and_makeup_arrangement_are_enqueued_with_outbox_messages()
    {
        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 term = new AcademicTerm
        {
            Code = "2026-EXAM-JOB",
            Name = "考试后台任务测试",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 7),
            EndDate = new DateOnly(2027, 1, 17)
        };
        var formalPlan = new ExamPlan
        {
            AcademicTerm = term,
            Name = "正式考试草稿"
        };
        var makeupPlan = new MakeupExamPlan
        {
            AcademicTerm = term,
            Name = "补考草稿"
        };
        db.AddRange(term, formalPlan, makeupPlan);
        await db.SaveChangesAsync();

        var scope = new ManagerDataScope();
        var exams = new ExamsController(db, scope, NoOpAppCache.Instance);
        var makeup = new MakeupExamsController(
            db,
            scope,
            new MakeupExamEligibilityService(db));

        var formalResult = await exams.AutoArrange(
            formalPlan.Id,
            new ExamAutoArrangeRequest(),
            CancellationToken.None);
        var makeupResult = await makeup.AutoArrange(
            makeupPlan.Id,
            new ExamAutoArrangeRequest(),
            CancellationToken.None);

        Assert.IsType<AcceptedAtActionResult>(formalResult);
        Assert.IsType<AcceptedAtActionResult>(makeupResult);

        var jobs = await db.ExamArrangementJobs
            .OrderBy(x => x.Kind)
            .ToListAsync();
        Assert.Equal(2, jobs.Count);
        Assert.Equal(ExamArrangementKind.FormalExam, jobs[0].Kind);
        Assert.Equal(formalPlan.Id, jobs[0].ActivePlanId);
        Assert.Equal(ExamArrangementKind.MakeupExam, jobs[1].Kind);
        Assert.Equal(makeupPlan.Id, jobs[1].ActivePlanId);
        Assert.All(jobs, job =>
            Assert.Equal(ExamArrangementJobStatus.Queued, job.Status));

        var outbox = await db.BackgroundJobOutboxMessages.ToListAsync();
        Assert.Equal(2, outbox.Count);
        Assert.All(outbox, message =>
        {
            Assert.Equal(BackgroundJobKind.ExamArrangement, message.JobKind);
            Assert.Equal(BackgroundJobOutboxState.Pending, message.State);
        });
        Assert.Equal(
            jobs.Select(x => x.Id).Order(),
            outbox.Select(x => x.JobId).Order());
    }

    [Fact]
    public async Task Repeated_formal_arrangement_returns_the_existing_active_job()
    {
        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 term = new AcademicTerm
        {
            Code = "2026-EXAM-DUP",
            Name = "考试任务去重测试",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 7),
            EndDate = new DateOnly(2027, 1, 17)
        };
        var plan = new ExamPlan
        {
            AcademicTerm = term,
            Name = "正式考试草稿"
        };
        db.AddRange(term, plan);
        await db.SaveChangesAsync();

        var controller = new ExamsController(
            db,
            new ManagerDataScope(),
            NoOpAppCache.Instance);

        var first = Assert.IsType<AcceptedAtActionResult>(
            await controller.AutoArrange(
                plan.Id,
                new ExamAutoArrangeRequest(),
                CancellationToken.None));
        var second = Assert.IsType<AcceptedAtActionResult>(
            await controller.AutoArrange(
                plan.Id,
                new ExamAutoArrangeRequest(),
                CancellationToken.None));

        Assert.Equal(202, first.StatusCode);
        Assert.Equal(202, second.StatusCode);
        Assert.Equal(1, await db.ExamArrangementJobs.CountAsync());
        Assert.Equal(1, await db.BackgroundJobOutboxMessages.CountAsync());
    }

    [Fact]
    public async Task Processor_records_business_failure_and_releases_active_plan()
    {
        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 term = new AcademicTerm
        {
            Code = "2026-EXAM-FAIL",
            Name = "考试任务失败测试",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 7),
            EndDate = new DateOnly(2027, 1, 17)
        };
        var plan = new ExamPlan
        {
            AcademicTerm = term,
            Name = "缺少节次配置的考试计划"
        };
        var job = new ExamArrangementJob
        {
            Kind = ExamArrangementKind.FormalExam,
            PlanId = plan.Id,
            ActivePlanId = plan.Id,
            AssignClassrooms = true,
            AssignInvigilators = true
        };
        db.AddRange(term, plan, job);
        await db.SaveChangesAsync();

        var processor = new ExamArrangementJobProcessor(
            db,
            new ExamArrangementService(db),
            new MakeupExamArrangementService(db),
            NoOpAppCache.Instance,
            NullLogger<ExamArrangementJobProcessor>.Instance);
        await processor.ProcessAsync(job.Id, CancellationToken.None);

        db.ChangeTracker.Clear();
        var failed = await db.ExamArrangementJobs.SingleAsync();
        Assert.Equal(ExamArrangementJobStatus.Failed, failed.Status);
        Assert.Null(failed.ActivePlanId);
        Assert.Equal("编排失败", failed.CurrentStep);
        Assert.False(string.IsNullOrWhiteSpace(failed.ErrorMessage));
        Assert.NotNull(failed.CompletedAt);
    }

    private sealed class ManagerDataScope : ICurrentUserDataScope
    {
        public CurrentUserScope Current { get; } = new(
            Guid.NewGuid(),
            "考试管理员",
            null,
            DataScope.All,
            new HashSet<string> { SystemRoles.AcademicAdmin });
    }
}
