namespace Application.Tests.Students;

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

public class CreateStudentCommandTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly CreateStudentHandler _handler;

    public CreateStudentCommandTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _unitOfWork = Substitute.For<IUnitOfWork>();
        _handler = new CreateStudentHandler(_studentRepository, _unitOfWork);
    }

    [Fact]
    public async Task Handle_WithValidData_ShouldCreateStudent()
    {
        // Arrange
        var command = new CreateStudentCommand("John Doe", "john@example.com");

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

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("John Doe");
        result.Email.Should().Be("john@example.com");
        result.TotalCredits.Should().Be(0);
        result.Enrollments.Should().BeEmpty();

        _studentRepository.Received(1).Add(Arg.Any<Student>());
        await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WithInvalidEmail_ShouldThrow()
    {
        // Arrange
        var command = new CreateStudentCommand("John Doe", "invalid-email");

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

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

    [Fact]
    public async Task Handle_WithEmptyName_ShouldThrow()
    {
        // Arrange
        var command = new CreateStudentCommand("", "john@example.com");

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

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

public class UpdateStudentCommandTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly UpdateStudentHandler _handler;

    public UpdateStudentCommandTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _unitOfWork = Substitute.For<IUnitOfWork>();
        _handler = new UpdateStudentHandler(_studentRepository, _unitOfWork);
    }

    [Fact]
    public async Task Handle_WhenStudentExists_ShouldUpdateStudent()
    {
        // Arrange
        var student = new Student("Old Name", Email.Create("old@example.com"));
        SetEntityId(student, 1);

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

        var command = new UpdateStudentCommand(1, "New Name", "new@example.com");

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

        // Assert
        result.Should().NotBeNull();
        result.Name.Should().Be("New Name");
        result.Email.Should().Be("new@example.com");

        _studentRepository.Received(1).Update(student);
        await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
    }

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

        var command = new UpdateStudentCommand(999, "Name", "email@test.com");

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

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

    [Fact]
    public async Task Handle_WithInvalidEmail_ShouldThrow()
    {
        // Arrange
        var student = new Student("Name", Email.Create("old@example.com"));
        _studentRepository.GetByIdWithEnrollmentsAsync(1, Arg.Any<CancellationToken>())
            .Returns(student);

        var command = new UpdateStudentCommand(1, "Name", "invalid");

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

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

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

public class DeleteStudentCommandTests
{
    private readonly IStudentRepository _studentRepository;
    private readonly IUnitOfWork _unitOfWork;
    private readonly DeleteStudentHandler _handler;

    public DeleteStudentCommandTests()
    {
        _studentRepository = Substitute.For<IStudentRepository>();
        _unitOfWork = Substitute.For<IUnitOfWork>();
        _handler = new DeleteStudentHandler(_studentRepository, _unitOfWork);
    }

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

        var command = new DeleteStudentCommand(1);

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

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

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

        var command = new DeleteStudentCommand(999);

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

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