namespace Application.Tests.Auth;

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

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

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

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

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

    [Fact]
    public async Task Handle_WithValidActivationCode_ShouldActivateAccount()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
            .Returns(student);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new ActivateAccountCommand("VALIDCODE123", "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_WithInvalidActivationCode_ShouldReturnError()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("INVALIDCODE", Arg.Any<string>())
            .Returns(false);

        var command = new ActivateAccountCommand("INVALIDCODE", "newuser", "password123");

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

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

    [Fact]
    public async Task Handle_WithExpiredActivationCode_ShouldReturnError()
    {
        // Arrange
        var student = CreateStudentWithExpiredActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");

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

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

    [Fact]
    public async Task Handle_WithExistingUsername_ShouldReturnError()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("existinguser", Arg.Any<CancellationToken>())
            .Returns(true);

        var command = new ActivateAccountCommand("VALIDCODE123", "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
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "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_ShouldCreateUserWithStudentRole()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        SetEntityId(student, 5);
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _studentRepository.GetByIdAsync(5, Arg.Any<CancellationToken>())
            .Returns(student);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");

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

        // Assert
        await _userRepository.Received(1).AddAsync(
            Arg.Is<User>(u => u.Role == UserRoles.Student && u.StudentId == 5),
            Arg.Any<CancellationToken>()
        );
    }

    [Fact]
    public async Task Handle_ShouldGenerateRecoveryCode()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
            .Returns(student);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");

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

        // Assert
        result.RecoveryCode.Should().NotBeNullOrEmpty();
        result.RecoveryCode!.Length.Should().Be(12);
    }

    [Fact]
    public async Task Handle_ShouldClearActivationCodeAfterSuccess()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
            .Returns(student);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("new.jwt.token");

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");

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

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

    [Fact]
    public async Task Handle_ShouldReturnJwtTokenForAutoLogin()
    {
        // Arrange
        var student = CreateStudentWithActivationCode("John Doe", "john@test.com", "activation_hash");
        _studentRepository.GetPendingActivationAsync(Arg.Any<CancellationToken>())
            .Returns(new List<Student> { student });
        _passwordService.VerifyPassword("VALIDCODE123", "activation_hash")
            .Returns(true);
        _userRepository.ExistsAsync("newuser", Arg.Any<CancellationToken>())
            .Returns(false);
        _studentRepository.GetByIdAsync(student.Id, Arg.Any<CancellationToken>())
            .Returns(student);
        _jwtService.GenerateToken(Arg.Any<User>())
            .Returns("auto.login.token");

        var command = new ActivateAccountCommand("VALIDCODE123", "newuser", "password123");

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

        // Assert
        result.Token.Should().Be("auto.login.token");
        _jwtService.Received(1).GenerateToken(Arg.Any<User>());
    }

    private static Student CreateStudentWithActivationCode(string name, string email, string activationHash)
    {
        var student = new Student(name, Email.Create(email));
        typeof(Student).GetProperty("ActivationCodeHash")?.SetValue(student, activationHash);
        typeof(Student).GetProperty("ActivationExpiresAt")?.SetValue(student, DateTime.UtcNow.AddDays(2));
        return student;
    }

    private static Student CreateStudentWithExpiredActivationCode(string name, string email, string activationHash)
    {
        var student = new Student(name, Email.Create(email));
        typeof(Student).GetProperty("ActivationCodeHash")?.SetValue(student, activationHash);
        typeof(Student).GetProperty("ActivationExpiresAt")?.SetValue(student, DateTime.UtcNow.AddDays(-1));
        return student;
    }

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