using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
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;

namespace Jiaowu.Api.Tests;

public sealed class ExamDeletionControllerTests
{
    [Fact]
    public async Task RemoveSessionsBatch_ValidatesCompleteSelectionBeforeRemoving()
    {
        await using var fixture = await Fixture.CreateAsync();
        var controller = fixture.CreateController();
        var selectedIds = fixture.DraftPlan.Sessions
            .Select(x => x.Id)
            .ToArray();
        var otherPlanSessionId = fixture.PublishedPlan.Sessions.Single().Id;

        var invalidResult = await controller.RemoveSessionsBatch(
            fixture.DraftPlan.Id,
            new RemoveExamSessionsBatchRequest(
                [selectedIds[0], otherPlanSessionId]),
            CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(invalidResult);
        Assert.Equal(2, await fixture.Db.ExamSessions.CountAsync(
            x => x.ExamPlanId == fixture.DraftPlan.Id));

        var result = Assert.IsType<OkObjectResult>(
            await controller.RemoveSessionsBatch(
                fixture.DraftPlan.Id,
                new RemoveExamSessionsBatchRequest(selectedIds),
                CancellationToken.None));

        Assert.Equal(2, ReadIntProperty(result.Value!, "removedCount"));
        Assert.False(await fixture.Db.ExamSessions.AnyAsync(
            x => x.ExamPlanId == fixture.DraftPlan.Id));
        Assert.False(await fixture.Db.ExamSessionInvigilators.AnyAsync(
            x => selectedIds.Contains(x.ExamSessionId)));
        Assert.True(await fixture.Db.ExamPlans.AnyAsync(
            x => x.Id == fixture.DraftPlan.Id));
    }

    [Fact]
    public async Task RemoveSessionsBatch_RejectsPublishedPlan()
    {
        await using var fixture = await Fixture.CreateAsync();
        var controller = fixture.CreateController();
        var sessionId = fixture.PublishedPlan.Sessions.Single().Id;

        var result = await controller.RemoveSessionsBatch(
            fixture.PublishedPlan.Id,
            new RemoveExamSessionsBatchRequest([sessionId]),
            CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(result);
        Assert.True(await fixture.Db.ExamSessions.AnyAsync(
            x => x.Id == sessionId));
    }

    [Fact]
    public async Task DeletePlan_DeletesDraftWithSessions_AndRejectsPublishedPlan()
    {
        await using var fixture = await Fixture.CreateAsync();
        var controller = fixture.CreateController();
        var draftPlanId = fixture.DraftPlan.Id;
        var publishedPlanId = fixture.PublishedPlan.Id;
        var draftSessionIds = fixture.DraftPlan.Sessions
            .Select(x => x.Id)
            .ToArray();

        var publishedResult = await controller.DeletePlan(
            publishedPlanId,
            CancellationToken.None);

        Assert.IsType<ConflictObjectResult>(publishedResult);
        Assert.True(await fixture.Db.ExamPlans.AnyAsync(
            x => x.Id == publishedPlanId));

        var draftResult = await controller.DeletePlan(
            draftPlanId,
            CancellationToken.None);

        Assert.IsType<NoContentResult>(draftResult);
        Assert.False(await fixture.Db.ExamPlans.AnyAsync(
            x => x.Id == draftPlanId));
        Assert.False(await fixture.Db.ExamSessions.AnyAsync(
            x => x.ExamPlanId == draftPlanId));
        Assert.False(await fixture.Db.ExamSessionInvigilators.AnyAsync(
            x => draftSessionIds.Contains(x.ExamSessionId)));
        Assert.True(await fixture.Db.ExamPlans.AnyAsync(
            x => x.Id == publishedPlanId));
    }

    private static int ReadIntProperty(object value, string name) =>
        (int)value.GetType().GetProperty(name)!.GetValue(value)!;

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

        private Fixture(
            SqliteConnection connection,
            AppDbContext db,
            ExamPlan draftPlan,
            ExamPlan publishedPlan)
        {
            this.connection = connection;
            Db = db;
            DraftPlan = draftPlan;
            PublishedPlan = publishedPlan;
        }

        public AppDbContext Db { get; }
        public ExamPlan DraftPlan { get; }
        public ExamPlan PublishedPlan { get; }

        public static async Task<Fixture> 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 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 course = new Course
            {
                Code = "CS101",
                Name = "程序设计基础",
                CollegeId = college.Id,
                Credits = 4,
                TotalHours = 64,
                LectureHours = 48,
                PracticeHours = 16,
                Nature = CourseNature.MajorRequired,
                AssessmentMethod = AssessmentMethod.Examination
            };
            var firstTask = CreateTask(
                term.Id,
                course.Id,
                "2026-1-CS101-01");
            var secondTask = CreateTask(
                term.Id,
                course.Id,
                "2026-1-CS101-02");
            var invigilator = new Teacher
            {
                TeacherNumber = "T001",
                Name = "张老师",
                CollegeId = college.Id
            };
            var firstDraftSession = CreateSession(
                firstTask.Id,
                new DateOnly(2027, 1, 8));
            firstDraftSession.Invigilators =
            [
                new ExamSessionInvigilator
                {
                    TeacherId = invigilator.Id
                }
            ];
            var draftPlan = new ExamPlan
            {
                AcademicTermId = term.Id,
                Name = "期末考试草稿",
                Sessions =
                [
                    firstDraftSession,
                    CreateSession(secondTask.Id, new DateOnly(2027, 1, 9))
                ]
            };
            var publishedPlan = new ExamPlan
            {
                AcademicTermId = term.Id,
                Name = "已发布期末考试",
                Status = ExamPlanStatus.Published,
                PublishedAt = DateTime.UtcNow,
                Sessions =
                [
                    CreateSession(firstTask.Id, new DateOnly(2027, 1, 10))
                ]
            };

            db.AddRange(
                college,
                term,
                course,
                firstTask,
                secondTask,
                invigilator,
                draftPlan,
                publishedPlan);
            await db.SaveChangesAsync();

            return new Fixture(
                connection,
                db,
                draftPlan,
                publishedPlan);
        }

        public ExamsController CreateController() => new(
            Db,
            new ManagerDataScope(),
            NoOpAppCache.Instance);

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

        private static TeachingTask CreateTask(
            Guid termId,
            Guid courseId,
            string number) => new()
        {
            AcademicTermId = termId,
            CourseId = courseId,
            TaskNumber = number,
            Name = $"教学班 {number}",
            Capacity = 60,
            Status = TeachingTaskStatus.Published
        };

        private static ExamSession CreateSession(
            Guid taskId,
            DateOnly examDate) => new()
        {
            TeachingTaskId = taskId,
            ExamDate = examDate,
            StartPeriod = 1,
            PeriodCount = 2,
            StartsAt = examDate.ToDateTime(new TimeOnly(8, 0)),
            EndsAt = examDate.ToDateTime(new TimeOnly(9, 50)),
            RequiredInvigilatorCount = 2
        };
    }

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