namespace Application.Tests.Auth;

using Application.Auth;
using Application.Auth.Commands;
using Domain.Entities;
using Domain.Ports.Repositories;
using FluentAssertions;
using NSubstitute;
using Xunit;

public class RegisterCommandTests
{
    private readonly IUserRepository _userRepository;
    private readonly IStudentRepository _studentRepository;
    private readonly IPasswordService _passwordService;
    private readonly IJwtService _jwtService;
    private readonly IUnitOfWork _unitOfWork;
    private readonly RegisterCommandHandler _handler;

    public RegisterCommandTests()
    {
        _userRepository = Substitute.For<IUserRepository>();
        _studentRepository = Substitute.For<IStudentRepository>();
        _passwordService = Substitute.For<IPasswordService>();
        _jwtService = Substitute.For<IJwtService>();
        _unitOfWork = Substitute.For<IUnitOfWork>();

        _passwordService.HashPassword(Arg.Any<string>()).Returns("hashed_value");

        _handler = new RegisterCommandHandler(
            _userRepository,
            _studentRepository,
            _passwordService,
            _jwtService,
            _unitOfWork
        );
    }

    [Fact]
    public async Task Handle_WithValidData_ShouldCreateUser()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand("newuser", "password123");

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

        // Assert
        result.Success.Should().BeTrue();
        result.Token.Should().Be("new.jwt.token");
        result.RecoveryCode.Should().NotBeNullOrEmpty();
        result.Error.Should().BeNull();
    }

    [Fact]
    public async Task Handle_WithExistingUsername_ShouldReturnError()
    {
        // Arrange
        _userRepository.ExistsAsync("existinguser", Arg.Any<CancellationToken>())
            .Returns(true);

        var command = new RegisterCommand("existinguser", "password123");

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

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().Contain("ya existe");
    }

    [Fact]
    public async Task Handle_WithShortPassword_ShouldReturnError()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);

        var command = new RegisterCommand("newuser", "12345"); // 5 chars, less than 6

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

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().Contain("al menos 6 caracteres");
    }

    [Fact]
    public async Task Handle_WithNameAndEmail_ShouldCreateStudent()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand(
            "newuser",
            "password123",
            Name: "John Doe",
            Email: "john@example.com"
        );

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

        // Assert
        result.Success.Should().BeTrue();
        _studentRepository.Received(1).Add(Arg.Is<Student>(s => s.Name == "John Doe"));
        await _unitOfWork.Received().SaveChangesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WithInvalidEmail_ShouldReturnError()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);

        var command = new RegisterCommand(
            "newuser",
            "password123",
            Name: "John Doe",
            Email: "invalid-email"
        );

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

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().NotBeNullOrEmpty();
    }

    [Fact]
    public async Task Handle_ShouldGenerateRecoveryCode()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand("newuser", "password123");

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

        // Assert
        result.RecoveryCode.Should().NotBeNullOrEmpty();
        result.RecoveryCode!.Length.Should().Be(12);
        result.RecoveryCode.Should().MatchRegex("^[A-Z0-9]+$");
    }

    [Fact]
    public async Task Handle_ShouldHashPasswordBeforeSaving()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand("newuser", "password123");

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

        // Assert
        _passwordService.Received(1).HashPassword("password123");
        await _userRepository.Received(1).AddAsync(
            Arg.Is<User>(u => u.PasswordHash == "hashed_value"),
            Arg.Any<CancellationToken>()
        );
    }

    [Fact]
    public async Task Handle_ShouldNormalizeUsernameToLowercase()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand("NEWUSER", "password123");

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

        // Assert
        result.Success.Should().BeTrue();
        result.User!.Username.Should().Be("newuser");
    }

    [Fact]
    public async Task Handle_NewUserShouldHaveStudentRole()
    {
        // Arrange
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new RegisterCommand("newuser", "password123");

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

        // Assert
        result.User!.Role.Should().Be(UserRoles.Student);
    }
}
