using Jiaowu.Api.Controllers;
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 CourseAdjustmentsControllerTests
{
    [Fact]
    public async Task Reschedule_MovesOnlyTheSelectedOccurrence()
    {
        await using var fixture = await Fixture.CreateAsync();
        var teacherController = new CourseAdjustmentsController(
            fixture.Db,
            new TestScope(fixture.TeacherUserId, DataScope.Self, SystemRoles.Teacher));
        var targetDate = new DateOnly(2026, 9, 24);

        var created = await teacherController.Create(
            new CourseAdjustmentRequest(
                fixture.Task.Id,
                CourseAdjustmentType.Reschedule,
                "参加学院教研活动",
                true,
                targetDate,
                null,
                3,
                null,
                fixture.Classroom.Id,
                null,
                null,
                null,
                fixture.Entry.Id,
                3),
            CancellationToken.None);
        Assert.IsType<CreatedResult>(created);

        var adjustment = await fixture.Db.CourseAdjustments.SingleAsync();
        Assert.Equal(new DateOnly(2026, 9, 16), adjustment.SourceDate);
        Assert.Equal(1, adjustment.SourceStartPeriod);
        Assert.Equal(2, adjustment.SourcePeriodCount);

        var adminController = new CourseAdjustmentsController(
            fixture.Db,
            new TestScope(Guid.NewGuid(), DataScope.All, SystemRoles.SuperAdmin));
        var approved = await adminController.Approve(
            adjustment.Id,
            CancellationToken.None);
        Assert.IsType<NoContentResult>(approved);

        var entries = await fixture.Db.ScheduleEntries
            .OrderBy(x => x.StartWeek)
            .ThenBy(x => x.DayOfWeek)
            .ToListAsync();
        Assert.Contains(entries, x =>
            x.DayOfWeek == 3 && x.StartWeek == 1 && x.EndWeek == 2);
        Assert.Contains(entries, x =>
            x.DayOfWeek == 3 && x.StartWeek == 4 && x.EndWeek == 5);
        Assert.Contains(entries, x =>
            x.DayOfWeek == 4 && x.StartWeek == 4 && x.EndWeek == 4 &&
            x.StartPeriod == 3 && x.PeriodCount == 2);
        Assert.DoesNotContain(entries, x =>
            x.DayOfWeek == 3 && x.StartWeek <= 3 && x.EndWeek >= 3);
    }

    [Fact]
    public async Task Cancel_RemovesOnlyTheSelectedMiddleWeek()
    {
        await using var fixture = await Fixture.CreateAsync();
        var teacherController = new CourseAdjustmentsController(
            fixture.Db,
            new TestScope(fixture.TeacherUserId, DataScope.Self, SystemRoles.Teacher));
        await teacherController.Create(
            new CourseAdjustmentRequest(
                fixture.Task.Id,
                CourseAdjustmentType.Cancel,
                "参加培训",
                true,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                null,
                fixture.Entry.Id,
                3),
            CancellationToken.None);
        var adjustment = await fixture.Db.CourseAdjustments.SingleAsync();

        var adminController = new CourseAdjustmentsController(
            fixture.Db,
            new TestScope(Guid.NewGuid(), DataScope.All, SystemRoles.SuperAdmin));
        var approved = await adminController.Approve(
            adjustment.Id,
            CancellationToken.None);
        Assert.IsType<NoContentResult>(approved);

        var entries = await fixture.Db.ScheduleEntries
            .OrderBy(x => x.StartWeek)
            .ToListAsync();
        Assert.Equal(2, entries.Count);
        Assert.Contains(entries, x => x.StartWeek == 1 && x.EndWeek == 2);
        Assert.Contains(entries, x => x.StartWeek == 4 && x.EndWeek == 5);
    }

    private sealed class Fixture : IAsyncDisposable
    {
        private readonly SqliteConnection connection;

        private Fixture(
            SqliteConnection connection,
            AppDbContext db,
            Guid teacherUserId,
            TeachingTask task,
            ScheduleEntry entry,
            Classroom classroom)
        {
            this.connection = connection;
            Db = db;
            TeacherUserId = teacherUserId;
            Task = task;
            Entry = entry;
            Classroom = classroom;
        }

        public AppDbContext Db { get; }
        public Guid TeacherUserId { get; }
        public TeachingTask Task { get; }
        public ScheduleEntry Entry { get; }
        public Classroom Classroom { get; }

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

            var teacherUserId = Guid.NewGuid();
            var user = new ApplicationUser
            {
                Id = teacherUserId,
                UserName = "T001",
                NormalizedUserName = "T001",
                DisplayName = "张老师"
            };
            var campus = new Campus { Code = "MAIN", Name = "主校区" };
            var college = new College
            {
                Code = "CS",
                Name = "计算机学院",
                CampusId = campus.Id
            };
            var building = new Building
            {
                Code = "A",
                Name = "教学楼 A",
                CampusId = campus.Id
            };
            var classroom = new Classroom
            {
                Code = "A101",
                Name = "A101",
                BuildingId = building.Id,
                Capacity = 60
            };
            var teacher = new Teacher
            {
                TeacherNumber = "T001",
                Name = "张老师",
                CollegeId = college.Id,
                UserId = teacherUserId
            };
            var course = new Course
            {
                Code = "CS101",
                Name = "程序设计基础",
                CollegeId = college.Id,
                Credits = 4,
                TotalHours = 64,
                LectureHours = 48,
                PracticeHours = 16,
                Nature = CourseNature.MajorRequired,
                AssessmentMethod = AssessmentMethod.Examination
            };
            var term = 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)
            };
            var task = new TeachingTask
            {
                TaskNumber = "2026-1-CS101-01",
                Name = "程序设计基础教学班",
                AcademicTermId = term.Id,
                CourseId = course.Id,
                Capacity = 60,
                Status = TeachingTaskStatus.Published,
                Teachers =
                [
                    new TeachingTaskTeacher
                    {
                        TeacherId = teacher.Id,
                        IsPrimary = true
                    }
                ]
            };
            var plan = new SchedulePlan
            {
                AcademicTermId = term.Id,
                Name = "正式课表",
                Version = "1.0",
                Status = SchedulePlanStatus.Published
            };
            var entry = new ScheduleEntry
            {
                SchedulePlanId = plan.Id,
                TeachingTaskId = task.Id,
                ClassroomId = classroom.Id,
                DayOfWeek = 3,
                StartPeriod = 1,
                PeriodCount = 2,
                StartWeek = 1,
                EndWeek = 5,
                WeekPattern = WeekPattern.All
            };
            db.AddRange(
                user,
                campus,
                college,
                building,
                classroom,
                teacher,
                course,
                term,
                task,
                plan,
                entry);
            await db.SaveChangesAsync();
            return new Fixture(connection, db, teacherUserId, task, entry, classroom);
        }

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

    private sealed class TestScope(
        Guid userId,
        DataScope scope,
        string role) : ICurrentUserDataScope
    {
        public CurrentUserScope Current { get; } = new(
            userId,
            "测试用户",
            null,
            scope,
            new HashSet<string>([role]));
    }
}
