using BCrypt.Net;
using Knot.Contracts.Auth.Application.Abstractions;
using Knot.Contracts.Auth.Application.Auth.DTOs;
using Knot.Contracts.Settings.Application.Abstractions;
using Knot.Contracts.Settings.Application.DTOs;
using Knot.Modules.Auth.Domain;
using Knot.Shared.Kernel;

namespace Knot.Modules.Auth.Application.Users.Register;

public sealed record RegisterUserCommand(
    string Username,
    string Password,
    string DisplayName,
    string? Email,
    string? Bio) : ICommand<AuthResponseDto>;

internal sealed class RegisterUserCommandHandler : ICommandHandler<RegisterUserCommand, AuthResponseDto>
{
    private readonly IUserRepository _userRepository;
    private readonly IAuthUnitOfWork _unitOfWork;
    private readonly ISystemSettings _settings;
    private readonly IJwtTokenProvider _tokenProvider;

    public RegisterUserCommandHandler(
        IUserRepository userRepository,
        IAuthUnitOfWork unitOfWork,
        ISystemSettings settings,
        IJwtTokenProvider tokenProvider)
    {
        _userRepository = userRepository;
        _unitOfWork = unitOfWork;
        _settings = settings;
        _tokenProvider = tokenProvider;
    }

    public async Task<Result<AuthResponseDto>> Handle(RegisterUserCommand request, CancellationToken cancellationToken)
    {
        if (!_settings.Current.EnableRegistration)
        {
            return Result.Failure<AuthResponseDto>(AuthErrors.IdentityRegistrationDisabled);
        }

        if (!await _userRepository.IsUsernameUniqueAsync(request.Username, cancellationToken))
        {
            return Result.Failure<AuthResponseDto>(AuthErrors.IdentityUsernameNotUnique);
        }

        string passwordHash = BCrypt.Net.BCrypt.HashPassword(request.Password);

        var user = User.Create(
            request.Username,
            passwordHash,
            request.DisplayName,
            request.Email,
            request.Bio);

        var repoImpl = _userRepository as Infrastructure.Persistence.UserRepository;
        repoImpl?.Add(user);

        await _unitOfWork.SaveChangesAsync(cancellationToken);

        // Get the saved user as contract
        var userContract = await _userRepository.GetByUsernameAsync(request.Username, cancellationToken);
        if (userContract == null)
        {
            return Result.Failure<AuthResponseDto>(AuthErrors.IdentityRegistrationFailed);
        }

        string token = _tokenProvider.Generate(userContract.Id, userContract.Username, userContract.DisplayName, userContract.Avatar);
        string refreshToken = _tokenProvider.GenerateRefreshToken();
        DateTime refreshExpiry = _tokenProvider.GetRefreshTokenExpiry();

        userContract.SetRefreshToken(refreshToken, refreshExpiry);
        await _userRepository.UpdateAsync(userContract, cancellationToken);

        return Result.Success(new AuthResponseDto
        {
            AccessToken = token,
            RefreshToken = refreshToken,
            UserId = userContract.Id,
            Username = userContract.Username,
            DisplayName = userContract.DisplayName
        });
    }
}

