using System.Text;
using Jiaowu.Api.Domain.Academic;
using Jiaowu.Api.Domain.Identity;
using Jiaowu.Api.Infrastructure.Auth;
using Jiaowu.Api.Infrastructure.Persistence;
using Jiaowu.Api.Infrastructure.Timetables;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

namespace Jiaowu.Api.Tests;

public sealed class PersonalCalendarServiceTests : IAsyncLifetime
{
    private readonly SqliteConnection _connection = new("Data Source=:memory:");
    private AppDbContext _db = null!;

    public async Task InitializeAsync()
    {
        await _connection.OpenAsync();
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseSqlite(_connection)
            .Options;
        _db = new AppDbContext(options);
        await _db.Database.EnsureCreatedAsync();
    }

    public async Task DisposeAsync()
    {
        await _db.DisposeAsync();
        await _connection.DisposeAsync();
    }

    [Fact]
    public async Task Build_creates_live_teacher_feed_with_courses_and_exams()
    {
        var today = DateOnly.FromDateTime(DateTime.Today);
        var mondayOffset = today.DayOfWeek == DayOfWeek.Sunday
            ? 6
            : (int)today.DayOfWeek - 1;
        var termStart = today.AddDays(-mondayOffset);
        var campus = new Campus { Code = "MAIN", Name = "主校区" };
        var college = new College
        {
            Code = "CS",
            Name = "计算机学院",
            CampusId = campus.Id
        };
        var user = new ApplicationUser
        {
            Id = Guid.NewGuid(),
            UserName = "calendar-teacher",
            NormalizedUserName = "CALENDAR-TEACHER",
            DisplayName = "陈老师",
            CalendarSubscriptionStamp = PersonalCalendarService.CreateStamp(),
            CalendarSubscriptionCreatedAt = DateTime.UtcNow
        };
        var teacher = new Teacher
        {
            TeacherNumber = "T-CALENDAR",
            Name = "陈老师",
            CollegeId = college.Id,
            UserId = user.Id,
            Status = TeacherStatus.Active
        };
        var course = new Course
        {
            Code = "CS101-CALENDAR",
            Name = "程序设计基础",
            CollegeId = college.Id,
            Credits = 4,
            TotalHours = 64,
            LectureHours = 48,
            PracticeHours = 16,
            Nature = CourseNature.MajorRequired,
            AssessmentMethod = AssessmentMethod.Examination
        };
        var term = new AcademicTerm
        {
            Code = "CALENDAR-TERM",
            Name = "个人日历测试学期",
            AcademicYear = "2026-2027",
            Season = TermSeason.Autumn,
            StartDate = termStart,
            EndDate = termStart.AddDays(40),
            IsCurrent = true
        };
        var building = new Building
        {
            Code = "J-CALENDAR",
            Name = "第一教学楼",
            CampusId = campus.Id
        };
        var classroom = new Classroom
        {
            Code = "J-CALENDAR-101",
            Name = "101",
            BuildingId = building.Id,
            Capacity = 60
        };
        _db.AddRange(
            campus,
            college,
            user,
            teacher,
            course,
            term,
            building,
            classroom);
        await _db.SaveChangesAsync();

        var scheduledTask = new TeachingTask
        {
            TaskNumber = "CALENDAR-TASK-01",
            Name = "程序设计基础教学班",
            AcademicTermId = term.Id,
            CourseId = course.Id,
            Capacity = 60,
            Status = TeachingTaskStatus.Published,
            Teachers =
            [
                new TeachingTaskTeacher { TeacherId = teacher.Id, IsPrimary = true }
            ]
        };
        var flexibleTask = new TeachingTask
        {
            TaskNumber = "CALENDAR-TASK-02",
            Name = "程序设计实践",
            AcademicTermId = term.Id,
            CourseId = course.Id,
            Capacity = 60,
            Status = TeachingTaskStatus.Published,
            SchedulingMode = TeachingTaskSchedulingMode.Flexible,
            StartWeek = 1,
            EndWeek = 4,
            WeeklyHours = 2,
            Teachers =
            [
                new TeachingTaskTeacher { TeacherId = teacher.Id, IsPrimary = true }
            ]
        };
        var plan = new SchedulePlan
        {
            AcademicTermId = term.Id,
            Name = "正式课表",
            Version = "V1",
            Status = SchedulePlanStatus.Published,
            PublishedAt = DateTime.UtcNow
        };
        _db.AddRange(
            scheduledTask,
            flexibleTask,
            plan,
            new ScheduleTimeSlot
            {
                AcademicTermId = term.Id,
                PeriodNumber = 1,
                Name = "第一节",
                StartsAt = new TimeOnly(8, 0),
                EndsAt = new TimeOnly(8, 45)
            },
            new ScheduleTimeSlot
            {
                AcademicTermId = term.Id,
                PeriodNumber = 2,
                Name = "第二节",
                StartsAt = new TimeOnly(8, 55),
                EndsAt = new TimeOnly(9, 40)
            });
        await _db.SaveChangesAsync();

        _db.ScheduleEntries.Add(new ScheduleEntry
        {
            SchedulePlanId = plan.Id,
            TeachingTaskId = scheduledTask.Id,
            ClassroomId = classroom.Id,
            DayOfWeek = 1,
            StartPeriod = 1,
            PeriodCount = 2,
            StartWeek = 1,
            EndWeek = 2,
            WeekPattern = WeekPattern.All
        });
        var examDate = termStart.AddDays(5);
        var examPlan = new ExamPlan
        {
            AcademicTermId = term.Id,
            Name = "期末考试",
            Status = ExamPlanStatus.Published,
            PublishedAt = DateTime.UtcNow
        };
        var exam = new ExamSession
        {
            ExamPlan = examPlan,
            TeachingTaskId = scheduledTask.Id,
            ClassroomId = classroom.Id,
            ExamDate = examDate,
            StartPeriod = 1,
            PeriodCount = 2,
            StartsAt = examDate.ToDateTime(new TimeOnly(8, 0)),
            EndsAt = examDate.ToDateTime(new TimeOnly(9, 40)),
            Invigilators =
            [
                new ExamSessionInvigilator { TeacherId = teacher.Id }
            ]
        };
        var makeupPlan = new MakeupExamPlan
        {
            AcademicTermId = term.Id,
            Name = "补考安排",
            Status = MakeupExamPlanStatus.Published,
            PublishedAt = DateTime.UtcNow
        };
        var makeupDate = termStart.AddDays(12);
        var makeup = new MakeupExamSession
        {
            MakeupExamPlan = makeupPlan,
            TeachingTaskId = scheduledTask.Id,
            ClassroomId = classroom.Id,
            ExamDate = makeupDate,
            StartPeriod = 1,
            PeriodCount = 2,
            StartsAt = makeupDate.ToDateTime(new TimeOnly(8, 0)),
            EndsAt = makeupDate.ToDateTime(new TimeOnly(9, 40)),
            Invigilators =
            [
                new MakeupExamSessionInvigilator { TeacherId = teacher.Id }
            ]
        };
        _db.AddRange(exam, makeup);
        await _db.SaveChangesAsync();

        var service = CreateService();
        var result = await service.BuildAsync(user, CancellationToken.None);

        Assert.NotNull(result);
        Assert.Equal(5, result.EventCount);
        var ics = Encoding.UTF8.GetString(result.Content);
        Assert.Contains("REFRESH-INTERVAL;VALUE=DURATION:PT1H", ics);
        Assert.Contains("SUMMARY:程序设计基础", ics);
        Assert.Contains("SUMMARY:[监考] 程序设计基础", ics);
        Assert.Contains("SUMMARY:[补考监考] 程序设计基础", ics);
        Assert.Contains("SUMMARY:[灵活安排] 程序设计基础", ics);
        Assert.Contains(
            $"DTSTART:{termStart:yyyyMMdd}T000000Z",
            ics);
        Assert.All(
            ics.Split("\r\n", StringSplitOptions.RemoveEmptyEntries),
            line => Assert.True(
                Encoding.UTF8.GetByteCount(line) <= 75,
                $"ICS line exceeds 75 octets: {line}"));
    }

    [Fact]
    public async Task Build_creates_student_feed_with_booked_experiment()
    {
        var today = DateOnly.FromDateTime(DateTime.Today);
        var mondayOffset = today.DayOfWeek == DayOfWeek.Sunday
            ? 6
            : (int)today.DayOfWeek - 1;
        var termStart = today.AddDays(-mondayOffset);
        var user = new ApplicationUser
        {
            Id = Guid.NewGuid(),
            UserName = "calendar-student",
            NormalizedUserName = "CALENDAR-STUDENT",
            DisplayName = "实验学生",
            CalendarSubscriptionStamp = PersonalCalendarService.CreateStamp(),
            CalendarSubscriptionCreatedAt = DateTime.UtcNow
        };
        var campus = new Campus { Code = "LAB-MAIN", Name = "主校区" };
        var college = new College
        {
            Code = "LAB-CS",
            Name = "计算机学院",
            CampusId = campus.Id
        };
        var major = new Major
        {
            Code = "LAB-SE",
            Name = "软件工程",
            CollegeId = college.Id,
            DegreeType = "工学"
        };
        var administrativeClass = new AdministrativeClass
        {
            Code = "LAB-CLASS",
            Name = "软件工程实验班",
            MajorId = major.Id,
            Grade = today.Year
        };
        var student = new Student
        {
            StudentNumber = "LAB-STUDENT-01",
            Name = "实验学生",
            AdministrativeClassId = administrativeClass.Id,
            EnrollmentYear = today.Year,
            EnrollmentDate = termStart,
            UserId = user.Id,
            Status = StudentStatus.Active
        };
        var teacher = new Teacher
        {
            TeacherNumber = "LAB-TEACHER-01",
            Name = "实验教师",
            CollegeId = college.Id,
            Status = TeacherStatus.Active
        };
        var course = new Course
        {
            Code = "LAB-CALENDAR",
            Name = "嵌入式系统实验",
            CollegeId = college.Id,
            Credits = 2,
            TotalHours = 32,
            LectureHours = 16,
            PracticeHours = 16,
            Nature = CourseNature.Practice,
            AssessmentMethod = AssessmentMethod.Assessment
        };
        var term = new AcademicTerm
        {
            Code = "LAB-CALENDAR-TERM",
            Name = "实验日历测试学期",
            AcademicYear = $"{today.Year}-{today.Year + 1}",
            Season = TermSeason.Autumn,
            StartDate = termStart,
            EndDate = termStart.AddDays(40),
            IsCurrent = true
        };
        var building = new Building
        {
            Code = "LAB-CENTER",
            Name = "实验中心",
            CampusId = campus.Id
        };
        var classroom = new Classroom
        {
            Code = "LAB-CENTER-301",
            Name = "301",
            BuildingId = building.Id,
            Capacity = 30
        };
        _db.AddRange(
            user,
            campus,
            college,
            major,
            administrativeClass,
            student,
            teacher,
            course,
            term,
            building,
            classroom);
        await _db.SaveChangesAsync();

        var task = new TeachingTask
        {
            TaskNumber = "LAB-CALENDAR-TASK",
            Name = "嵌入式系统实验教学班",
            AcademicTermId = term.Id,
            CourseId = course.Id,
            Capacity = 30,
            Status = TeachingTaskStatus.Published,
            Teachers =
            [
                new TeachingTaskTeacher
                {
                    TeacherId = teacher.Id,
                    IsPrimary = true
                }
            ],
            Classes =
            [
                new TeachingTaskClass
                {
                    AdministrativeClassId = administrativeClass.Id
                }
            ]
        };
        _db.AddRange(
            task,
            new ScheduleTimeSlot
            {
                AcademicTermId = term.Id,
                PeriodNumber = 3,
                Name = "第三节",
                StartsAt = new TimeOnly(10, 0),
                EndsAt = new TimeOnly(10, 45)
            },
            new ScheduleTimeSlot
            {
                AcademicTermId = term.Id,
                PeriodNumber = 4,
                Name = "第四节",
                StartsAt = new TimeOnly(10, 55),
                EndsAt = new TimeOnly(11, 40)
            });
        await _db.SaveChangesAsync();

        var project = new ExperimentProject
        {
            TeachingTaskId = task.Id,
            Code = "LAB-SELF-01",
            Name = "自主硬件实验",
            ArrangementMode = ExperimentArrangementMode.SelfScheduled,
            StartDate = termStart,
            EndDate = termStart.AddDays(14),
            Status = ExperimentProjectStatus.Published,
            PublishedAt = DateTime.UtcNow
        };
        var session = new ExperimentSession
        {
            ExperimentProject = project,
            ClassroomId = classroom.Id,
            SessionDate = termStart.AddDays(2),
            StartPeriod = 3,
            PeriodCount = 2,
            Capacity = 30,
            ReservedCount = 1
        };
        session.Bookings.Add(new ExperimentBooking
        {
            ExperimentProject = project,
            StudentId = student.Id,
            Status = ExperimentBookingStatus.Booked,
            BookedAt = DateTime.UtcNow
        });
        _db.Add(session);
        await _db.SaveChangesAsync();

        var result = await CreateService().BuildAsync(
            user,
            CancellationToken.None);

        Assert.NotNull(result);
        Assert.Equal(1, result.EventCount);
        var ics = Encoding.UTF8.GetString(result.Content);
        Assert.Contains("SUMMARY:[实验] 自主硬件实验", ics);
        Assert.Contains("CATEGORIES:实验", ics);
        Assert.Contains("LOCATION:主校区 · 实验中心 · 301", ics);
        Assert.Contains("LAB-SELF-01 · 自主预约", ics);
    }

    [Fact]
    public void Access_token_is_bound_to_user_stamp_and_can_be_rotated()
    {
        var user = new ApplicationUser
        {
            Id = Guid.NewGuid(),
            UserName = "calendar-user",
            DisplayName = "订阅用户",
            CalendarSubscriptionStamp = PersonalCalendarService.CreateStamp()
        };
        var service = CreateService();
        var originalToken = service.CreateAccessToken(user);

        Assert.True(service.IsAccessTokenValid(user, originalToken));
        Assert.False(service.IsAccessTokenValid(user, originalToken + "x"));

        user.CalendarSubscriptionStamp = PersonalCalendarService.CreateStamp();
        Assert.False(service.IsAccessTokenValid(user, originalToken));
        Assert.True(service.IsAccessTokenValid(user, service.CreateAccessToken(user)));

        user.CalendarSubscriptionStamp = null;
        Assert.False(service.IsAccessTokenValid(user, originalToken));
    }

    private PersonalCalendarService CreateService() => new(
        _db,
        new TimetableDataService(_db),
        Options.Create(new JwtOptions
        {
            Key = "calendar-test-signing-key-with-at-least-thirty-two-characters"
        }));
}
