namespace Application.Tests.Enrollments;

using Application.Enrollments.Queries;
using Domain.Entities;
using Domain.Exceptions;
using Domain.Ports.Repositories;
using Domain.ValueObjects;
using FluentAssertions;
using NSubstitute;
using Xunit;

public class GetClassmatesQueryTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly IEnrollmentRepository _enrollmentRepository;
    private readonly GetClassmatesHandler _handler;

    public GetClassmatesQueryTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _enrollmentRepository = Substitute.For<IEnrollmentRepository>();
        _handler = new GetClassmatesHandler(_studentRepository, _enrollmentRepository);
    }

    [Fact]
    public async Task Handle_WhenStudentNotFound_ShouldThrow()
    {
        // Arrange
        _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
            .Returns((Student?)null);

        var query = new GetClassmatesQuery(1);

        // Act
        var act = () => _handler.Handle(query, CancellationToken.None);

        // Assert
        await act.Should().ThrowAsync<StudentNotFoundException>();
    }

    [Fact]
    public async Task Handle_WhenStudentHasNoEnrollments_ShouldReturnEmptyList()
    {
        // Arrange
        var student = new Student("John Doe", Email.Create("john@test.com"));
        _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
            .Returns(student);

        var query = new GetClassmatesQuery(1);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().BeEmpty();
        await _enrollmentRepository.DidNotReceive()
            .GetClassmatesBatchAsync(Arg.Any<int>(), Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WhenStudentHasEnrollments_ShouldUseBatchQuery()
    {
        // Arrange
        var student = new Student("John Doe", Email.Create("john@test.com"));
        SetEntityId(student, 1);

        // Create enrollments with IDs
        var enrollment1 = new Enrollment(1, 1);
        var enrollment2 = new Enrollment(1, 2);
        SetEntityId(enrollment1, 1);
        SetEntityId(enrollment2, 2);

        // Use reflection to set Subject property for name access
        var subject1 = new Subject("Math", 1);
        var subject2 = new Subject("Physics", 2);
        SetEntityId(subject1, 1);
        SetEntityId(subject2, 2);
        SetNavigationProperty(enrollment1, "Subject", subject1);
        SetNavigationProperty(enrollment2, "Subject", subject2);

        student.AddEnrollment(enrollment1);
        student.AddEnrollment(enrollment2);

        _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
            .Returns(student);

        var classmate1 = new Student("Jane Doe", Email.Create("jane@test.com"));
        var classmate2 = new Student("Bob Smith", Email.Create("bob@test.com"));
        SetEntityId(classmate1, 2);
        SetEntityId(classmate2, 3);

        var batchResult = new Dictionary<int, IReadOnlyList<Student>>
        {
            [1] = [classmate1],
            [2] = [classmate2]
        };

        _enrollmentRepository.GetClassmatesBatchAsync(1, Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
            .Returns(batchResult);

        var query = new GetClassmatesQuery(1);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().HaveCount(2);
        result.Should().Contain(r => r.SubjectId == 1 && r.Classmates.Any(c => c.Name == "Jane Doe"));
        result.Should().Contain(r => r.SubjectId == 2 && r.Classmates.Any(c => c.Name == "Bob Smith"));

        await _enrollmentRepository.Received(1)
            .GetClassmatesBatchAsync(1, Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WhenNoClassmatesFound_ShouldReturnEmptyClassmatesLists()
    {
        // Arrange
        var student = new Student("John Doe", Email.Create("john@test.com"));
        SetEntityId(student, 1);

        var enrollment = new Enrollment(1, 1);
        SetEntityId(enrollment, 1);
        var subject = new Subject("Math", 1);
        SetEntityId(subject, 1);
        SetNavigationProperty(enrollment, "Subject", subject);
        student.AddEnrollment(enrollment);

        _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
            .Returns(student);

        var emptyBatchResult = new Dictionary<int, IReadOnlyList<Student>>();
        _enrollmentRepository.GetClassmatesBatchAsync(1, Arg.Any<IEnumerable<int>>(), Arg.Any<CancellationToken>())
            .Returns(emptyBatchResult);

        var query = new GetClassmatesQuery(1);

        // Act
        var result = await _handler.Handle(query, CancellationToken.None);

        // Assert
        result.Should().HaveCount(1);
        result[0].Classmates.Should().BeEmpty();
    }

    private static void SetEntityId<T>(T entity, int id) where T : class
    {
        var prop = typeof(T).GetProperty("Id");
        prop?.SetValue(entity, id);
    }

    private static void SetNavigationProperty<T>(T entity, string propertyName, object value) where T : class
    {
        var prop = typeof(T).GetProperty(propertyName);
        prop?.SetValue(entity, value);
    }
}
