using Jiaowu.Api.Controllers;
using Jiaowu.Api.Contracts;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Tests;

public sealed class ApprovalsControllerTests
{
    [Fact]
    public async Task Pending_returns_a_single_server_page_with_total()
    {
        await using var fixture = await ApprovalFixture.CreateAsync();
        fixture.Db.AddRange(
            new DeferredExam
            {
                StudentId = fixture.Student.Id,
                TeachingTaskId = fixture.CurrentTask.Id,
                Reason = "较早提交",
                SubmittedAt = DateTime.UtcNow.AddMinutes(-2)
            },
            new CourseExemption
            {
                StudentId = fixture.Student.Id,
                TeachingTaskId = fixture.CurrentTask.Id,
                Reason = "较晚提交",
                SubmittedAt = DateTime.UtcNow.AddMinutes(-1)
            });
        await fixture.Db.SaveChangesAsync();

        var controller = new ApprovalsController(
            fixture.Db,
            new ManagerDataScope());
        var result = await controller.GetPending(2, 1, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result.Result);
        var page = Assert.IsType<PagedResult<ApprovalItem>>(ok.Value);
        Assert.Equal(2, page.Total);
        Assert.Equal(2, page.Page);
        var item = Assert.Single(page.Items);
        Assert.Equal("较早提交", item.Desc);
    }

    [Fact]
    public async Task MyCourses_IncludesPublishedScheduledTaskAfterCurrentTermChanges()
    {
        await using var fixture = await ApprovalFixture.CreateAsync();
        fixture.CurrentTerm.IsCurrent = false;
        fixture.Db.AcademicTerms.Add(new AcademicTerm
        {
            Code = "2026-2",
            Name = "2026—2027 学年第二学期",
            AcademicYear = "2026-2027",
            Season = TermSeason.Spring,
            StartDate = new DateOnly(2027, 2, 20),
            EndDate = new DateOnly(2027, 7, 1),
            IsCurrent = true
        });
        fixture.Db.SchedulePlans.Add(new SchedulePlan
        {
            AcademicTermId = fixture.CurrentTerm.Id,
            Name = "正式课表",
            Version = "v1",
            Status = SchedulePlanStatus.Published,
            Entries =
            [
                new ScheduleEntry
                {
                    TeachingTaskId = fixture.CurrentTask.Id,
                    DayOfWeek = 1,
                    StartPeriod = 1,
                    PeriodCount = 2,
                    StartWeek = 1,
                    EndWeek = 16,
                    WeekPattern = WeekPattern.All
                }
            ]
        });
        await fixture.Db.SaveChangesAsync();

        var result = await fixture.Controller.GetMyEnrolledCourses(
            CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var options = Assert.IsAssignableFrom<
            IEnumerable<StudentApprovalCourseOption>>(ok.Value).ToList();
        var option = Assert.Single(options);
        Assert.Equal(fixture.CurrentTask.Id, option.TeachingTaskId);
        Assert.Equal(fixture.CurrentCourse.Id, option.CourseId);
        Assert.True(option.HasPublishedSchedule);
    }

    [Fact]
    public async Task Exemption_RejectsTeachingTaskOutsideStudentsCurrentCourses()
    {
        await using var fixture = await ApprovalFixture.CreateAsync();
        var unrelatedTask = new TeachingTask
        {
            TaskNumber = "2026-1-OTHER-01",
            Name = "其他教学班",
            AcademicTermId = fixture.CurrentTerm.Id,
            CourseId = fixture.SubstituteCourse.Id,
            Capacity = 30,
            Status = TeachingTaskStatus.Published
        };
        fixture.Db.TeachingTasks.Add(unrelatedTask);
        await fixture.Db.SaveChangesAsync();

        var result = await fixture.Controller.ApplyExemption(
            new ExemptionRequest(unrelatedTask.Id, "申请原因"),
            CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(result);
        Assert.Empty(fixture.Db.CourseExemptions);
    }

    [Fact]
    public async Task Substitution_AcceptsCurrentCourseAndPublishedPassedCourse()
    {
        await using var fixture = await ApprovalFixture.CreateAsync();
        var historicalTerm = new AcademicTerm
        {
            Code = "2025-2",
            Name = "2025—2026 学年第二学期",
            AcademicYear = "2025-2026",
            Season = TermSeason.Spring,
            StartDate = new DateOnly(2026, 2, 20),
            EndDate = new DateOnly(2026, 7, 1)
        };
        var historicalTask = new TeachingTask
        {
            TaskNumber = "2025-2-PASS-01",
            Name = "已通过课程教学班",
            AcademicTermId = historicalTerm.Id,
            CourseId = fixture.SubstituteCourse.Id,
            Capacity = 30,
            Status = TeachingTaskStatus.Closed
        };
        var sheet = new GradeSheet
        {
            TeachingTaskId = historicalTask.Id,
            Status = GradeSheetStatus.Published,
            PublishedAt = DateTime.UtcNow.AddDays(-30),
            Records =
            [
                new GradeRecord
                {
                    StudentId = fixture.Student.Id,
                    TotalScore = 86,
                    GradePoint = 3.6m
                }
            ]
        };
        fixture.Db.AddRange(historicalTerm, historicalTask, sheet);
        await fixture.Db.SaveChangesAsync();

        var result = await fixture.Controller.ApplySubstitution(
            new SubstitutionRequest(
                fixture.CurrentCourse.Id,
                fixture.SubstituteCourse.Id,
                "课程内容相近"),
            CancellationToken.None);

        Assert.IsType<CreatedResult>(result);
        var substitution = Assert.Single(fixture.Db.CourseSubstitutions);
        Assert.Equal(fixture.CurrentCourse.Id, substitution.OriginalCourseId);
        Assert.Equal(
            fixture.SubstituteCourse.Id,
            substitution.SubstituteCourseId);
    }

    private sealed class ApprovalFixture : IAsyncDisposable
    {
        private ApprovalFixture(
            SqliteConnection connection,
            AppDbContext db,
            Student student,
            AcademicTerm currentTerm,
            Course currentCourse,
            Course substituteCourse,
            TeachingTask currentTask,
            ApprovalsController controller)
        {
            Connection = connection;
            Db = db;
            Student = student;
            CurrentTerm = currentTerm;
            CurrentCourse = currentCourse;
            SubstituteCourse = substituteCourse;
            CurrentTask = currentTask;
            Controller = controller;
        }

        private SqliteConnection Connection { get; }
        public AppDbContext Db { get; }
        public Student Student { get; }
        public AcademicTerm CurrentTerm { get; }
        public Course CurrentCourse { get; }
        public Course SubstituteCourse { get; }
        public TeachingTask CurrentTask { get; }
        public ApprovalsController Controller { get; }

        public static async Task<ApprovalFixture> CreateAsync()
        {
            var connection = new SqliteConnection("Data Source=:memory:");
            await connection.OpenAsync();
            var options = new DbContextOptionsBuilder<AppDbContext>()
                .UseSqlite(connection)
                .Options;
            var db = new AppDbContext(options);
            await db.Database.EnsureCreatedAsync();

            var college = new College { Code = "CS", Name = "计算机学院" };
            var major = new Major
            {
                Code = "080901",
                Name = "计算机科学与技术",
                CollegeId = college.Id,
                DegreeType = "工学学士"
            };
            var administrativeClass = new AdministrativeClass
            {
                Code = "CS2026-01",
                Name = "计科 2026-1 班",
                MajorId = major.Id,
                Grade = 2026
            };
            var user = new ApplicationUser
            {
                Id = Guid.NewGuid(),
                UserName = "202601001",
                NormalizedUserName = "202601001",
                DisplayName = "测试学生"
            };
            var student = new Student
            {
                StudentNumber = "202601001",
                Name = "测试学生",
                UserId = user.Id,
                AdministrativeClassId = administrativeClass.Id,
                EnrollmentYear = 2026,
                EnrollmentDate = new DateOnly(2026, 9, 1)
            };
            var currentTerm = new AcademicTerm
            {
                Code = "2026-1",
                Name = "2026—2027 学年第一学期",
                AcademicYear = "2026-2027",
                Season = TermSeason.Autumn,
                StartDate = new DateOnly(2026, 9, 1),
                EndDate = new DateOnly(2027, 1, 20),
                IsCurrent = true
            };
            var currentCourse = CreateCourse(college.Id, "CS101", "程序设计基础");
            var substituteCourse = CreateCourse(college.Id, "CS102", "程序设计进阶");
            var currentTask = new TeachingTask
            {
                TaskNumber = "2026-1-CS101-01",
                Name = "程序设计基础教学班",
                AcademicTermId = currentTerm.Id,
                CourseId = currentCourse.Id,
                Capacity = 60,
                Status = TeachingTaskStatus.Published,
                Classes =
                [
                    new TeachingTaskClass
                    {
                        AdministrativeClassId = administrativeClass.Id
                    }
                ]
            };
            db.AddRange(
                college,
                major,
                administrativeClass,
                user,
                student,
                currentTerm,
                currentCourse,
                substituteCourse,
                currentTask);
            await db.SaveChangesAsync();

            var controller = new ApprovalsController(
                db,
                new StudentDataScope(user.Id));
            return new ApprovalFixture(
                connection,
                db,
                student,
                currentTerm,
                currentCourse,
                substituteCourse,
                currentTask,
                controller);
        }

        public async ValueTask DisposeAsync()
        {
            await Db.DisposeAsync();
            await Connection.DisposeAsync();
        }

        private static Course CreateCourse(
            Guid collegeId,
            string code,
            string name) =>
            new()
            {
                Code = code,
                Name = name,
                CollegeId = collegeId,
                Credits = 3,
                TotalHours = 48,
                LectureHours = 32,
                PracticeHours = 16,
                Nature = CourseNature.MajorRequired,
                AssessmentMethod = AssessmentMethod.Examination
            };
    }

    private sealed class StudentDataScope(Guid userId) : ICurrentUserDataScope
    {
        public CurrentUserScope Current { get; } = new(
            userId,
            "测试学生",
            null,
            DataScope.Self,
            new HashSet<string>([SystemRoles.Student]));
    }

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