namespace Application.Tests.Enrollments;

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

public class UnenrollStudentCommandTests
{
    private readonly IEnrollmentRepository _enrollmentRepository;
    private readonly IStudentRepository _studentRepository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly UnenrollStudentHandler _handler;

    public UnenrollStudentCommandTests()
    {
        _enrollmentRepository = Substitute.For<IEnrollmentRepository>();
        _studentRepository = Substitute.For<IStudentRepository>();
        _unitOfWork = Substitute.For<IUnitOfWork>();
        _handler = new UnenrollStudentHandler(_enrollmentRepository, _studentRepository, _unitOfWork);
    }

    [Fact]
    public async Task Handle_WhenEnrollmentExists_ShouldUnenrollStudent()
    {
        // Arrange
        var enrollment = new Enrollment(1, 1);
        SetEntityId(enrollment, 1);

        var student = new Student("John Doe", Email.Create("john@example.com"));
        SetEntityId(student, 1);
        student.AddEnrollment(enrollment);

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

        var command = new UnenrollStudentCommand(1);

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

        // Assert
        result.Should().BeTrue();
        _enrollmentRepository.Received(1).Delete(enrollment);
        await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WhenEnrollmentNotFound_ShouldThrow()
    {
        // Arrange
        _enrollmentRepository.GetByIdAsync(999, Arg.Any<CancellationToken>())
            .Returns((Enrollment?)null);

        var command = new UnenrollStudentCommand(999);

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

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

    [Fact]
    public async Task Handle_WhenStudentNotFound_ShouldStillDeleteEnrollment()
    {
        // Arrange
        var enrollment = new Enrollment(999, 1);
        SetEntityId(enrollment, 1);

        _enrollmentRepository.GetByIdAsync(1, Arg.Any<CancellationToken>())
            .Returns(enrollment);
        _studentRepository.GetByIdWithEnrollmentsAsync(999, Arg.Any<CancellationToken>())
            .Returns((Student?)null);

        var command = new UnenrollStudentCommand(1);

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

        // Assert
        result.Should().BeTrue();
        _enrollmentRepository.Received(1).Delete(enrollment);
    }

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