using Jiaowu.Api.Controllers;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Infrastructure.Caching;
using Jiaowu.Api.Infrastructure.Persistence;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using System.Collections;
using System.Text.Encodings.Web;
using System.Text.Json;

namespace Jiaowu.Api.Tests;

public sealed class ScheduleSettingsControllerTests
{
    [Fact]
    public async Task Batch_constraints_assign_same_classroom_scope_to_all_tasks()
    {
        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 campus = new Campus { Code = "MAIN", Name = "主校区" };
        var building = new Building
        {
            Code = "J1",
            Name = "第一教学楼",
            Campus = campus
        };
        var classroom = new Classroom
        {
            Code = "J1-201",
            Name = "J1-201",
            Building = building,
            Capacity = 80
        };
        var college = new College { Code = "CS", Name = "计算机学院" };
        var course = new Course
        {
            Code = "CS101",
            Name = "程序设计基础",
            College = college,
            Credits = 4,
            TotalHours = 64,
            LectureHours = 48,
            PracticeHours = 16
        };
        var term = new AcademicTerm
        {
            Code = "2026-F",
            Name = "2026 秋季",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 7),
            EndDate = new DateOnly(2027, 1, 17)
        };
        var firstTask = NewTask("TASK-01", course, term);
        var secondTask = NewTask("TASK-02", course, term);
        db.AddRange(
            campus,
            building,
            classroom,
            college,
            course,
            term,
            firstTask,
            secondTask);
        await db.SaveChangesAsync();

        var controller = new ScheduleSettingsController(db, NoOpAppCache.Instance);
        var result = await controller.SaveConstraintsBatch(
            new TeachingTaskScheduleConstraintBatchRequest(
                term.Id,
                [firstTask.Id, secondTask.Id],
                null,
                null,
                null,
                true,
                campus.Id,
                building.Id,
                [classroom.Id],
                false,
                null,
                null,
                UpdateExperimentClassroomScope: true,
                AllowedExperimentVenueNatures: TeachingVenueNature.Laboratory,
                AllowedExperimentClassroomIds: [classroom.Id],
                ExperimentRequiredCampusId: campus.Id,
                ExperimentRequiredBuildingId: building.Id),
            CancellationToken.None);

        Assert.IsType<OkObjectResult>(result);
        db.ChangeTracker.Clear();
        var constraints = await db.TeachingTaskScheduleConstraints
            .Include(item => item.AllowedClassrooms)
            .Include(item => item.AllowedExperimentClassrooms)
            .OrderBy(item => item.TeachingTaskId)
            .ToListAsync();
        Assert.Equal(2, constraints.Count);
        Assert.All(constraints, constraint =>
        {
            Assert.True(constraint.RequiresClassroom);
            Assert.Equal(campus.Id, constraint.RequiredCampusId);
            Assert.Equal(building.Id, constraint.RequiredBuildingId);
            Assert.Equal(
                classroom.Id,
                Assert.Single(constraint.AllowedClassrooms).ClassroomId);
            Assert.Equal(campus.Id, constraint.ExperimentRequiredCampusId);
            Assert.Equal(building.Id, constraint.ExperimentRequiredBuildingId);
            Assert.Equal(
                classroom.Id,
                Assert.Single(constraint.AllowedExperimentClassrooms).ClassroomId);
        });
    }

    [Fact]
    public async Task Get_constraints_materializes_teachers_and_allowed_classrooms()
    {
        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 college = new College { Code = "CS", Name = "计算机学院" };
        var course = new Course
        {
            Code = "CS101",
            Name = "程序设计基础",
            College = college,
            Credits = 4,
            TotalHours = 64,
            LectureHours = 48,
            PracticeHours = 16
        };
        var term = new AcademicTerm
        {
            Code = "2026-F",
            Name = "2026 秋季",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = new DateOnly(2026, 9, 7),
            EndDate = new DateOnly(2027, 1, 17)
        };
        var teacher = new Teacher
        {
            TeacherNumber = "T001",
            Name = "测试教师",
            College = college
        };
        var classroom = new Classroom
        {
            Code = "J1-201",
            Name = "J1-201",
            Capacity = 80,
            Building = new Building
            {
                Code = "J1",
                Name = "第一教学楼",
                Campus = new Campus { Code = "MAIN", Name = "主校区" }
            }
        };
        var task = NewTask("TASK-01", course, term);
        task.Teachers.Add(new TeachingTaskTeacher
        {
            Teacher = teacher,
            IsPrimary = true
        });
        var constraint = new TeachingTaskScheduleConstraint
        {
            TeachingTask = task,
            AllowedDayOfWeeks = "1,3,5",
            EarliestPeriod = 1,
            LatestPeriod = 8,
            AllowedClassrooms =
            [
                new TeachingTaskAllowedClassroom { Classroom = classroom }
            ]
        };
        db.AddRange(college, course, term, teacher, classroom, task, constraint);
        await db.SaveChangesAsync();

        var result = await new ScheduleSettingsController(db, NoOpAppCache.Instance)
            .GetConstraints(term.Id, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var payload = Assert.IsAssignableFrom<IEnumerable>(ok.Value);
        Assert.Single(payload.Cast<object>());
        var json = JsonSerializer.Serialize(
            ok.Value,
            new JsonSerializerOptions
            {
                Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping
            });
        Assert.Contains("测试教师", json);
        Assert.Contains(classroom.Id.ToString(), json);
        Assert.Contains("\"WeeklyHours\":4", json);
        Assert.Contains("\"CoursePracticeHours\":16", json);
    }

    [Fact]
    public async Task MySql_schedule_settings_materialize_time_slots_and_constraints()
    {
        var connectionString =
            Environment.GetEnvironmentVariable("JIAOWU_TEST_MYSQL_CONNECTION");
        if (string.IsNullOrWhiteSpace(connectionString))
            return;

        var previousConnectionString =
            Environment.GetEnvironmentVariable("ConnectionStrings__MySql");
        AppDbContext db;
        try
        {
            Environment.SetEnvironmentVariable(
                "ConnectionStrings__MySql",
                connectionString);
            db = new AppDbContextFactory().CreateDbContext([]);
        }
        finally
        {
            Environment.SetEnvironmentVariable(
                "ConnectionStrings__MySql",
                previousConnectionString);
        }
        await using var ownedDb = db;
        var term = await db.AcademicTerms.FirstAsync();
        var course = await db.Courses.FirstAsync();
        var teacher = await db.Teachers.FirstAsync(
            item => item.CollegeId == course.CollegeId);
        var classroom = await db.Classrooms.FirstAsync();
        await using var transaction = await db.Database.BeginTransactionAsync();
        var periodNumber = await db.ScheduleTimeSlots
            .Where(item => item.AcademicTermId == term.Id)
            .Select(item => (int?)item.PeriodNumber)
            .MaxAsync() ?? 0;

        var task = new TeachingTask
        {
            TaskNumber = $"MYSQL-{Guid.NewGuid():N}"[..22],
            Name = "MySQL 排课约束回归",
            AcademicTermId = term.Id,
            CourseId = course.Id,
            Capacity = 60,
            WeeklyHours = 2,
            StartWeek = 1,
            EndWeek = 16,
            Status = TeachingTaskStatus.Published,
            Teachers =
            [
                new TeachingTaskTeacher
                {
                    TeacherId = teacher.Id,
                    IsPrimary = true
                }
            ]
        };
        db.Add(task);
        db.Add(new ScheduleTimeSlot
        {
            AcademicTermId = term.Id,
            PeriodNumber = periodNumber + 1,
            Name = "MySQL 回归节次",
            StartsAt = new TimeOnly(8, 0),
            EndsAt = new TimeOnly(8, 45)
        });
        db.Add(new TeachingTaskScheduleConstraint
        {
            TeachingTask = task,
            AllowedDayOfWeeks = "1,3,5",
            EarliestPeriod = 1,
            LatestPeriod = 8,
            AllowedClassrooms =
            [
                new TeachingTaskAllowedClassroom { ClassroomId = classroom.Id }
            ]
        });
        await db.SaveChangesAsync();
        db.ChangeTracker.Clear();

        var result = await new ScheduleSettingsController(db, NoOpAppCache.Instance)
            .GetConstraints(term.Id, CancellationToken.None);

        var ok = Assert.IsType<OkObjectResult>(result);
        var payload = Assert.IsAssignableFrom<IEnumerable>(ok.Value);
        Assert.Contains(payload.Cast<object>(), item =>
            JsonSerializer.Serialize(item).Contains(task.TaskNumber));
        var timeSlotsResult = await new ScheduleSettingsController(
                db,
                NoOpAppCache.Instance)
            .GetTimeSlots(term.Id, CancellationToken.None);
        var timeSlotsOk = Assert.IsType<OkObjectResult>(timeSlotsResult);
        var timeSlotsJson = JsonSerializer.Serialize(timeSlotsOk.Value);
        Assert.Contains("08:00", timeSlotsJson);

        var examTimeSlotsResult = await new ExamsController(
                db,
                null!,
                NoOpAppCache.Instance)
            .GetTimeSlotsForTerm(term.Id, CancellationToken.None);
        var examTimeSlotsOk =
            Assert.IsType<OkObjectResult>(examTimeSlotsResult);
        Assert.Contains(
            "08:00",
            JsonSerializer.Serialize(examTimeSlotsOk.Value));

        var makeupTimeSlotsResult = await new MakeupExamsController(
                db,
                null!,
                null!)
            .GetTimeSlotsForTerm(term.Id, CancellationToken.None);
        var makeupTimeSlotsOk =
            Assert.IsType<OkObjectResult>(makeupTimeSlotsResult);
        Assert.Contains(
            "08:00",
            JsonSerializer.Serialize(makeupTimeSlotsOk.Value));
        await transaction.RollbackAsync();
    }

    private static TeachingTask NewTask(
        string taskNumber,
        Course course,
        AcademicTerm term) =>
        new()
        {
            TaskNumber = taskNumber,
            Name = $"{course.Name}教学班",
            Course = course,
            AcademicTerm = term,
            Capacity = 60,
            WeeklyHours = 4,
            StartWeek = 1,
            EndWeek = 16,
            Status = TeachingTaskStatus.Published
        };
}
