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.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

namespace Jiaowu.Api.Tests;

public sealed class TeachingTasksControllerTests
{
    [Fact]
    public async Task Delete_AllowsArchivedTask_ButRejectsPublishedTask()
    {
        await using var database = await TestDatabase.CreateAsync();
        var archived = await database.AddTaskAsync("ARCHIVED", TeachingTaskStatus.Closed);
        var published = await database.AddTaskAsync("PUBLISHED", TeachingTaskStatus.Published);

        var deleteArchived = await database.Controller.Delete(
            archived.Id,
            CancellationToken.None);
        var deletePublished = await database.Controller.Delete(
            published.Id,
            CancellationToken.None);

        Assert.IsType<NoContentResult>(deleteArchived);
        Assert.IsType<ConflictObjectResult>(deletePublished);
        Assert.False(await database.Db.TeachingTasks.AnyAsync(x => x.Id == archived.Id));
        Assert.True(await database.Db.TeachingTasks.AnyAsync(x => x.Id == published.Id));
    }

    [Fact]
    public async Task Unarchive_RestoresArchivedTaskToPublished()
    {
        await using var database = await TestDatabase.CreateAsync();
        var task = await database.AddTaskAsync("RESTORE", TeachingTaskStatus.Closed);

        var result = await database.Controller.Unarchive(task.Id, CancellationToken.None);

        Assert.IsType<NoContentResult>(result);
        await database.Db.Entry(task).ReloadAsync();
        Assert.Equal(TeachingTaskStatus.Published, task.Status);
    }

    [Fact]
    public async Task Batch_UnarchivesOnlyArchivedTasks()
    {
        await using var database = await TestDatabase.CreateAsync();
        var first = await database.AddTaskAsync("FIRST", TeachingTaskStatus.Closed);
        var second = await database.AddTaskAsync("SECOND", TeachingTaskStatus.Closed);

        var result = await database.Controller.Batch(
            new TeachingTaskBatchRequest(
                TeachingTaskBatchOperation.Unarchive,
                [first.Id, second.Id]),
            CancellationToken.None);

        Assert.IsType<OkObjectResult>(result);
        Assert.All(
            await database.Db.TeachingTasks
                .Where(x => x.Id == first.Id || x.Id == second.Id)
                .ToListAsync(),
            task => Assert.Equal(TeachingTaskStatus.Published, task.Status));
    }

    [Fact]
    public async Task Options_ReturnsAllPublishedTasksWithoutListPageTruncation()
    {
        await using var database = await TestDatabase.CreateAsync();
        for (var index = 1; index <= 125; index++)
        {
            await database.AddTaskAsync(
                $"OPTION-{index:D3}",
                TeachingTaskStatus.Published);
        }
        await database.AddTaskAsync("DRAFT", TeachingTaskStatus.Draft);

        var result = await database.Controller.GetOptions(
            database.TermId,
            TeachingTaskStatus.Published,
            CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var items = Assert.IsAssignableFrom<System.Collections.IEnumerable>(ok.Value);
        Assert.Equal(125, items.Cast<object>().Count());
    }

    [Fact]
    public async Task Options_PreservesCollegeDataScope()
    {
        await using var database = await TestDatabase.CreateAsync(collegeScoped: true);
        await database.AddTaskAsync("IN-SCOPE", TeachingTaskStatus.Published);
        var otherCollege = new College { Code = "OTHER", Name = "其他学院" };
        var otherCourse = new Course
        {
            Code = "OTHER101",
            Name = "范围外课程",
            College = otherCollege,
            Nature = CourseNature.MajorRequired,
            TotalHours = 32,
            LectureHours = 32
        };
        database.Db.Add(new TeachingTask
        {
            TaskNumber = "TASK-OUT-OF-SCOPE",
            Name = "范围外教学任务",
            AcademicTermId = database.TermId,
            Course = otherCourse,
            Capacity = 40,
            StartWeek = 1,
            EndWeek = 16,
            WeeklyHours = 2,
            Status = TeachingTaskStatus.Published,
            PublishedAt = DateTime.UtcNow
        });
        await database.Db.SaveChangesAsync();

        var result = await database.Controller.GetOptions(
            database.TermId,
            TeachingTaskStatus.Published,
            CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var items = Assert.IsAssignableFrom<System.Collections.IEnumerable>(ok.Value);
        Assert.Single(items.Cast<object>());
    }

    private sealed class TestDatabase : IAsyncDisposable
    {
        private readonly SqliteConnection connection;
        private readonly AcademicTerm term;
        private readonly Course course;

        private TestDatabase(
            SqliteConnection connection,
            AppDbContext db,
            AcademicTerm term,
            Course course,
            ICurrentUserDataScope dataScope)
        {
            this.connection = connection;
            Db = db;
            this.term = term;
            this.course = course;
            Controller = new TeachingTasksController(
                db,
                dataScope,
                NoOpAppCache.Instance);
        }

        public AppDbContext Db { get; }
        public TeachingTasksController Controller { get; }
        public Guid TermId => term.Id;

        public static async Task<TestDatabase> CreateAsync(bool collegeScoped = false)
        {
            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 = "TEST", 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, 15)
            };
            var course = new Course
            {
                Code = "TEST101",
                Name = "测试课程",
                CollegeId = college.Id,
                Nature = CourseNature.MajorRequired,
                TotalHours = 32,
                LectureHours = 32
            };
            db.AddRange(college, term, course);
            await db.SaveChangesAsync();
            return new TestDatabase(
                connection,
                db,
                term,
                course,
                new TestDataScope(collegeScoped ? college.Id : null));
        }

        public async Task<TeachingTask> AddTaskAsync(
            string suffix,
            TeachingTaskStatus status)
        {
            var task = new TeachingTask
            {
                TaskNumber = $"TASK-{suffix}",
                Name = $"教学任务 {suffix}",
                AcademicTermId = term.Id,
                CourseId = course.Id,
                Capacity = 40,
                StartWeek = 1,
                EndWeek = 16,
                WeeklyHours = 2,
                Status = status,
                PublishedAt = status == TeachingTaskStatus.Draft
                    ? null
                    : DateTime.UtcNow
            };
            Db.TeachingTasks.Add(task);
            await Db.SaveChangesAsync();
            return task;
        }

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

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

        public CurrentUserScope Current { get; }
    }
}
