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 LoginCommandTests
{
    private readonly IUserRepository _userRepository;
    private readonly IPasswordService _passwordService;
    private readonly IJwtService _jwtService;
    private readonly LoginCommandHandler _handler;

    public LoginCommandTests()
    {
        _userRepository = Substitute.For<IUserRepository>();
        _passwordService = Substitute.For<IPasswordService>();
        _jwtService = Substitute.For<IJwtService>();
        _handler = new LoginCommandHandler(_userRepository, _passwordService, _jwtService);
    }

    [Fact]
    public async Task Handle_WithValidCredentials_ShouldReturnSuccessWithToken()
    {
        // Arrange
        var user = CreateUser("testuser", "hashed_password");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("password123", "hashed_password")
            .Returns(true);
        _jwtService.GenerateToken(user)
            .Returns("valid.jwt.token");

        var command = new LoginCommand("testuser", "password123");

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

        // Assert
        result.Should().NotBeNull();
        result.Success.Should().BeTrue();
        result.Token.Should().Be("valid.jwt.token");
        result.User.Should().NotBeNull();
        result.User!.Username.Should().Be("testuser");
        result.Error.Should().BeNull();
    }

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

        var command = new LoginCommand("nonexistent", "password123");

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

        // Assert
        result.Success.Should().BeFalse();
        result.Token.Should().BeNull();
        result.Error.Should().Contain("incorrectos");
    }

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

        var command = new LoginCommand("testuser", "wrongpassword");

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

        // Assert
        result.Success.Should().BeFalse();
        result.Token.Should().BeNull();
        result.Error.Should().Contain("incorrectos");
    }

    [Fact]
    public async Task Handle_ShouldNormalizUsernameToLowercase()
    {
        // Arrange
        var user = CreateUser("testuser", "hashed_password");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("password123", "hashed_password")
            .Returns(true);
        _jwtService.GenerateToken(user)
            .Returns("valid.jwt.token");

        var command = new LoginCommand("TESTUSER", "password123");

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

        // Assert
        result.Success.Should().BeTrue();
        await _userRepository.Received(1).GetByUsernameAsync("testuser", Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_ShouldUpdateLastLoginOnSuccess()
    {
        // Arrange
        var user = CreateUser("testuser", "hashed_password");
        _userRepository.GetByUsernameAsync("testuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("password123", "hashed_password")
            .Returns(true);
        _jwtService.GenerateToken(user)
            .Returns("valid.jwt.token");

        var command = new LoginCommand("testuser", "password123");

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

        // Assert
        await _userRepository.Received(1).UpdateAsync(user, Arg.Any<CancellationToken>());
    }

    [Fact]
    public async Task Handle_WithStudentUser_ShouldIncludeStudentInfo()
    {
        // Arrange
        var user = CreateUserWithStudent("studentuser", "hashed_password", 1, "John Doe");
        _userRepository.GetByUsernameAsync("studentuser", Arg.Any<CancellationToken>())
            .Returns(user);
        _passwordService.VerifyPassword("password123", "hashed_password")
            .Returns(true);
        _jwtService.GenerateToken(user)
            .Returns("valid.jwt.token");

        var command = new LoginCommand("studentuser", "password123");

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

        // Assert
        result.Success.Should().BeTrue();
        result.User!.StudentId.Should().Be(1);
        result.User.StudentName.Should().Be("John Doe");
        result.User.Role.Should().Be(UserRoles.Student);
    }

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

    private static User CreateUserWithStudent(string username, string passwordHash, int studentId, string studentName)
    {
        var user = User.Create(username, passwordHash, "recovery_hash", UserRoles.Student, studentId);
        // Set up the Student navigation property
        var student = new Student(studentName, Domain.ValueObjects.Email.Create($"{username}@test.com"));
        typeof(User).GetProperty("Student")?.SetValue(user, student);
        return user;
    }
}
