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 ResetPasswordCommandTests
{
    private readonly IUserRepository _userRepository;
    private readonly IPasswordService _passwordService;
    private readonly IUnitOfWork _unitOfWork;
    private readonly ResetPasswordCommandHandler _handler;

    public ResetPasswordCommandTests()
    {
        _userRepository = Substitute.For<IUserRepository>();
        _passwordService = Substitute.For<IPasswordService>();
        _unitOfWork = Substitute.For<IUnitOfWork>();

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

        _handler = new ResetPasswordCommandHandler(
            _userRepository,
            _passwordService,
            _unitOfWork
        );
    }

    [Fact]
    public async Task Handle_WithValidRecoveryCode_ShouldResetPassword()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("VALIDCODE123", "recovery_code_hash")
            .Returns(true);

        var command = new ResetPasswordCommand("testuser", "VALIDCODE123", "newpassword123");

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

        // Assert
        result.Success.Should().BeTrue();
        result.Error.Should().BeNull();
    }

    [Fact]
    public async Task Handle_WithInvalidRecoveryCode_ShouldReturnError()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("INVALIDCODE", "recovery_code_hash")
            .Returns(false);

        var command = new ResetPasswordCommand("testuser", "INVALIDCODE", "newpassword123");

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

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

    [Fact]
    public async Task Handle_WithNonExistentUser_ShouldReturnError()
    {
        // Arrange
        _userRepository.GetByUsernameAsync("nonexistent", Arg.Any<CancellationToken>())
            .Returns((User?)null);

        var command = new ResetPasswordCommand("nonexistent", "ANYCODE123", "newpassword123");

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

        // Assert
        result.Success.Should().BeFalse();
        result.Error.Should().Contain("no encontrado");
    }

    [Fact]
    public async Task Handle_WithShortNewPassword_ShouldReturnError()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);

        var command = new ResetPasswordCommand("testuser", "VALIDCODE123", "12345"); // 5 chars

        // 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_ShouldHashNewPassword()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("VALIDCODE123", "recovery_code_hash")
            .Returns(true);

        var command = new ResetPasswordCommand("testuser", "VALIDCODE123", "newpassword123");

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

        // Assert
        _passwordService.Received(1).HashPassword("newpassword123");
    }

    [Fact]
    public async Task Handle_ShouldSaveChangesOnSuccess()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("VALIDCODE123", "recovery_code_hash")
            .Returns(true);

        var command = new ResetPasswordCommand("testuser", "VALIDCODE123", "newpassword123");

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

        // Assert
        await _unitOfWork.Received(1).SaveChangesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ShouldNotSaveChangesOnError()
    {
        // Arrange
        _userRepository.GetByUsernameAsync("nonexistent", Arg.Any<CancellationToken>())
            .Returns((User?)null);

        var command = new ResetPasswordCommand("nonexistent", "ANYCODE123", "newpassword123");

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

        // Assert
        await _unitOfWork.DidNotReceive().SaveChangesAsync(Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ShouldUpdateUserPasswordHash()
    {
        // Arrange
        var user = CreateUser("testuser", "old_password_hash", "recovery_code_hash");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("VALIDCODE123", "recovery_code_hash")
            .Returns(true);

        var command = new ResetPasswordCommand("testuser", "VALIDCODE123", "newpassword123");

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

        // Assert
        user.PasswordHash.Should().Be("new_hashed_password");
    }

    private static User CreateUser(string username, string passwordHash, string recoveryCodeHash)
    {
        return User.Create(username, passwordHash, recoveryCodeHash, UserRoles.Student);
    }
}
