using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging.Abstractions;
using NSubstitute;
using UniVerse.Application.DTOs.Sync;
using UniVerse.Application.Interfaces;
using UniVerse.Domain.Enums;
using UniVerse.Infrastructure.Data;
using UniVerse.Infrastructure.Services;
using Xunit;

namespace UniVerse.Api.Tests.Sync;

public class ScheduleSyncServiceTests
{
    private const string EventId = "48102128-2224-4cb9-ae8f-a91d0b7c512a";
    private const string CourseId = "73aa6226-adbb-4e15-b264-e16fee19fd73";
    private const string PersonId = "b5a5cad8-60c2-4d94-9972-8a0c2e981440";
    private const string FullName = "Иванов Иван Иванович";

    [Fact]
    public async Task SyncScheduleAsync_UsesRoomWorkingCapacityForLectureSeats()
    {
        await using var db = CreateDbContext();
        var modeus = Substitute.For<IModeusApiClient>();
        modeus.SearchEventsAsync(Arg.Any<SyncScheduleRequest>())
            .Returns(new ModeusEventsResponse
            {
                Embedded = new ModeusEventsEmbedded
                {
                    Events =
                    [
                        new ModeusEvent
                        {
                            Id = "event-1",
                            Name = "Open lecture",
                            StartsAt = new DateTime(2026, 5, 13, 9, 0, 0, DateTimeKind.Utc),
                            EndsAt = new DateTime(2026, 5, 13, 10, 30, 0, DateTimeKind.Utc)
                        }
                    ],
                    EventRooms =
                    [
                        new ModeusEventRoom
                        {
                            Links = new ModeusEventRoomLinks
                            {
                                Event = new ModeusHrefLink("/events/event-1"),
                                Room = new ModeusHrefLink("/rooms/room-1")
                            }
                        }
                    ],
                    Rooms =
                    [
                        new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: 60, WorkingCapacity: 42)
                    ],
                    EventTeams =
                    [
                        new ModeusEventTeam("event-1", 15)
                    ]
                }
            });

        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        var lecture = await db.Lectures.SingleAsync();
        Assert.Null(result.Error);
        Assert.Equal(1, result.Created);
        Assert.Equal(42, lecture.MaxEnrollments);
    }

    [Fact]
    public async Task SyncScheduleAsync_LoadsRoomCapacityWhenEventRoomHasNoCapacity()
    {
        await using var db = CreateDbContext();
        var modeus = Substitute.For<IModeusApiClient>();
        modeus.SearchEventsAsync(Arg.Any<SyncScheduleRequest>())
            .Returns(new ModeusEventsResponse
            {
                Embedded = new ModeusEventsEmbedded
                {
                    Events =
                    [
                        new ModeusEvent
                        {
                            Id = "event-1",
                            Name = "Open lecture",
                            StartsAt = new DateTime(2026, 5, 13, 9, 0, 0, DateTimeKind.Utc),
                            EndsAt = new DateTime(2026, 5, 13, 10, 30, 0, DateTimeKind.Utc)
                        }
                    ],
                    EventRooms =
                    [
                        new ModeusEventRoom
                        {
                            Links = new ModeusEventRoomLinks
                            {
                                Event = new ModeusHrefLink("/events/event-1"),
                                Room = new ModeusHrefLink("/rooms/room-1")
                            }
                        }
                    ],
                    Rooms =
                    [
                        new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: null, WorkingCapacity: null)
                    ],
                    EventTeams =
                    [
                        new ModeusEventTeam("event-1", 15)
                    ]
                }
            });
        modeus.SearchRoomsAsync()
            .Returns(new ModeusRoomsResponse
            {
                Rooms =
                [
                    new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: 60, WorkingCapacity: 48)
                ]
            });

        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        var lecture = await db.Lectures.SingleAsync();
        Assert.Null(result.Error);
        Assert.Equal(1, result.Created);
        Assert.Equal(48, lecture.MaxEnrollments);
    }

    [Fact]
    public async Task SyncScheduleAsync_SavesMandatoryAttendeesFromIctisStats()
    {
        await using var db = CreateDbContext();
        var modeus = Substitute.For<IModeusApiClient>();
        modeus.SearchEventsAsync(Arg.Any<SyncScheduleRequest>())
            .Returns(new ModeusEventsResponse
            {
                Embedded = new ModeusEventsEmbedded
                {
                    Events =
                    [
                        new ModeusEvent
                        {
                            Id = "event-1",
                            Name = "Open lecture",
                            StartsAt = new DateTime(2026, 5, 13, 9, 0, 0, DateTimeKind.Utc),
                            EndsAt = new DateTime(2026, 5, 13, 10, 30, 0, DateTimeKind.Utc),
                            IctisStats = new ModeusIctisStats(StudentCount: 30, TeacherCount: 1)
                        }
                    ],
                    EventRooms =
                    [
                        new ModeusEventRoom
                        {
                            Links = new ModeusEventRoomLinks
                            {
                                Event = new ModeusHrefLink("/events/event-1"),
                                Room = new ModeusHrefLink("/rooms/room-1")
                            }
                        }
                    ],
                    Rooms =
                    [
                        new ModeusRoom("room-1", "Room 101", "101", null, TotalCapacity: 120, WorkingCapacity: 120)
                    ]
                }
            });

        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        var lecture = await db.Lectures.SingleAsync();
        Assert.Null(result.Error);
        Assert.Equal(1, result.Created);
        Assert.Equal(120, lecture.MaxEnrollments);
        Assert.Equal(31, lecture.MandatoryAttendeesCount);
    }

    [Fact]
    public async Task SyncScheduleAsync_UsesModeusEventAttendeeTeacher()
    {
        await using var db = CreateDbContext();
        var modeus = new FakeModeusApiClient(BuildEventsResponse());
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        Assert.Equal(1, result.Created);

        var lecture = await db.Lectures.Include(item => item.Teacher).SingleAsync();
        Assert.Equal("Иванов Иван Иванович", lecture.Teacher?.DisplayName);
        Assert.Equal("modeus-b5a5cad8-60c2-4d94-9972-8a0c2e981440@modeus.local", lecture.Teacher?.Email);

        var teacherProfile = await db.TeacherProfiles.Include(item => item.User).SingleAsync();
        Assert.Equal("b5a5cad8-60c2-4d94-9972-8a0c2e981440", teacherProfile.ModeusId);
        Assert.Equal(teacherProfile.UserId, lecture.TeacherId);

        var teacherRole = await db.UserRoles.SingleAsync();
        Assert.Equal(lecture.TeacherId, teacherRole.UserId);
        Assert.Equal(UserRole.Teacher, teacherRole.Role);
    }

    [Fact]
    public async Task SyncScheduleAsync_SavesResolvedTeacherSubId()
    {
        await using var db = CreateDbContext();
        var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        var teacher = await db.Users.Include(user => user.TeacherProfile).SingleAsync();
        Assert.Equal("sso-sub-1", teacher.MicrosoftId);
        Assert.Equal($"modeus-{PersonId}@modeus.local", teacher.Email);
        Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
    }

    [Fact]
    public async Task SyncScheduleAsync_UsesPlaceholderWhenSubLookupFails()
    {
        await using var db = CreateDbContext();
        var modeus = new FakeModeusApiClient(BuildEventsResponse(), throwOnSubLookup: true);
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        var teacher = await db.Users.Include(user => user.TeacherProfile).SingleAsync();
        Assert.Null(teacher.MicrosoftId);
        Assert.Equal($"modeus-{PersonId}@modeus.local", teacher.Email);
        Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
    }

    [Fact]
    public async Task SyncScheduleAsync_AttachesTeacherProfileToExistingSsoUser()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new UniVerse.Domain.Entities.User
        {
            Id = 77,
            Email = "teacher@sfedu.ru",
            DisplayName = "Old Name",
            MicrosoftId = "sso-sub-1",
            Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 77, Role = UserRole.Student }]
        });
        await db.SaveChangesAsync();
        var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        Assert.Single(await db.Users.ToListAsync());
        var teacher = await db.Users.Include(user => user.Roles).Include(user => user.TeacherProfile).SingleAsync();
        Assert.Equal(77, teacher.Id);
        Assert.Equal("teacher@sfedu.ru", teacher.Email);
        Assert.Contains(teacher.Roles, role => role.Role == UserRole.Student);
        Assert.Contains(teacher.Roles, role => role.Role == UserRole.Teacher);
        Assert.Equal(PersonId, teacher.TeacherProfile?.ModeusId);
        Assert.True(await db.Lectures.AnyAsync(lecture => lecture.TeacherId == 77));
    }

    [Fact]
    public async Task SyncScheduleAsync_MergesPlaceholderIntoExistingSsoUserOnRetry()
    {
        await using var db = CreateDbContext();
        var placeholder = new UniVerse.Domain.Entities.User
        {
            Id = 10,
            Email = $"modeus-{PersonId}@modeus.local",
            DisplayName = FullName,
            Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }],
            TeacherProfile = new UniVerse.Domain.Entities.TeacherProfile { UserId = 10, ModeusId = PersonId }
        };
        db.Users.Add(placeholder);
        db.Users.Add(new UniVerse.Domain.Entities.User
        {
            Id = 20,
            Email = "teacher@sfedu.ru",
            DisplayName = FullName,
            MicrosoftId = "sso-sub-1",
            Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 20, Role = UserRole.Student }]
        });
        db.Courses.Add(new UniVerse.Domain.Entities.Course { Id = 1, Name = "Course", ExternalId = CourseId, IsSynced = true });
        db.Lectures.Add(new UniVerse.Domain.Entities.Lecture
        {
            Id = 1,
            CourseId = 1,
            TeacherId = 10,
            ExternalId = EventId,
            Title = "Old",
            StartsAt = DateTime.UtcNow,
            EndsAt = DateTime.UtcNow.AddHours(1)
        });
        await db.SaveChangesAsync();
        var modeus = new FakeModeusApiClient(BuildEventsResponse(), subId: "sso-sub-1");
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        Assert.Single(await db.Users.ToListAsync());
        var realUser = await db.Users.Include(user => user.Roles).Include(user => user.TeacherProfile).SingleAsync();
        Assert.Equal(20, realUser.Id);
        Assert.Equal(PersonId, realUser.TeacherProfile?.ModeusId);
        Assert.Contains(realUser.Roles, role => role.Role == UserRole.Teacher);
        Assert.True(await db.Lectures.AllAsync(lecture => lecture.TeacherId == 20));
    }

    [Fact]
    public async Task SyncScheduleAsync_DoesNotLookupSubWhenTeacherAlreadyHasMicrosoftId()
    {
        await using var db = CreateDbContext();
        db.Users.Add(new UniVerse.Domain.Entities.User
        {
            Id = 10,
            Email = "teacher@sfedu.ru",
            DisplayName = FullName,
            MicrosoftId = "sso-sub-1",
            Roles = [new UniVerse.Domain.Entities.UserRoleAssignment { UserId = 10, Role = UserRole.Teacher }],
            TeacherProfile = new UniVerse.Domain.Entities.TeacherProfile { UserId = 10, ModeusId = PersonId }
        });
        await db.SaveChangesAsync();
        var modeus = Substitute.For<IModeusApiClient>();
        modeus.SearchEventsAsync(Arg.Any<SyncScheduleRequest>()).Returns(BuildEventsResponse());
        var service = new ScheduleSyncService(db, modeus, NullLogger<ScheduleSyncService>.Instance);

        var result = await service.SyncScheduleAsync(new SyncScheduleRequest(null, null, null, null));

        Assert.Null(result.Error);
        await modeus.DidNotReceive().GetSubIdByFullNameAsync(Arg.Any<string>(), Arg.Any<CancellationToken>());
    }

    private static AppDbContext CreateDbContext()
    {
        var options = new DbContextOptionsBuilder<AppDbContext>()
            .UseInMemoryDatabase($"ScheduleSyncTests_{Guid.NewGuid()}")
            .Options;

        return new AppDbContext(options);
    }

    private static ModeusEventsResponse BuildEventsResponse()
    {
        const string attendeeId = "a894db4e-833f-4f52-a153-fdd7c7d32ca7";

        return new ModeusEventsResponse
        {
            Embedded = new ModeusEventsEmbedded
            {
                Events =
                [
                    new ModeusEvent
                    {
                        Id = EventId,
                        Name = "Тема 20. Управление ресурсами проекта. Часть 2.",
                        TypeId = "LAB",
                        StartsAt = new DateTime(2026, 4, 14, 5, 0, 0, DateTimeKind.Utc),
                        EndsAt = new DateTime(2026, 4, 14, 6, 35, 0, DateTimeKind.Utc),
                        Links = new ModeusEventLinks
                        {
                            CourseUnitRealization = new ModeusHrefLink($"/{CourseId}")
                        }
                    }
                ],
                CourseUnitRealizations =
                [
                    new ModeusCourseUnitRealization(
                        CourseId,
                        "Управление проектами разработки программного обеспечения",
                        "УПРПО")
                ],
                EventTeams = [new ModeusEventTeam(EventId, 25)],
                EventAttendees =
                [
                    new ModeusEventAttendee
                    {
                        Id = attendeeId,
                        RoleId = "TEACH",
                        RoleName = "Преподаватель",
                        Links = new ModeusEventAttendeeLinks
                        {
                            Event = new ModeusHrefLink($"/{EventId}"),
                            Person = new ModeusHrefLink($"/{PersonId}")
                        }
                    }
                ],
                Persons =
                [
                    new ModeusPerson(
                        PersonId,
                        "Иванов",
                        "Иван",
                        "Иванович",
                        FullName)
                ]
            }
        };
    }

    private sealed class FakeModeusApiClient(
        ModeusEventsResponse events,
        string? subId = null,
        bool throwOnSubLookup = false) : IModeusApiClient
    {
        public Task<ModeusEventsResponse> SearchEventsAsync(SyncScheduleRequest request) => Task.FromResult(events);

        public Task<ModeusRoomsResponse> SearchRoomsAsync() => Task.FromResult(new ModeusRoomsResponse());

        public Task<List<ModeusEmployee>> SearchEmployeeAsync(string fullname) => Task.FromResult(new List<ModeusEmployee>());

        public Task<string?> GetSubIdByFullNameAsync(string fullname, CancellationToken cancellationToken = default)
        {
            if (throwOnSubLookup)
                throw new HttpRequestException("lookup failed");

            return Task.FromResult(subId);
        }
    }
}
