using System;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FinlyticCore.Dtos;
using FinlyticCore.Services;
using FinlyticCore.Util;
using FinlyticNotify.Settings;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace FinlyticNotify.Services;
///
/// Service interface for resolving a UserId GUID to a clean username for ntfy channel addressing.
///
public interface IUserTradeResolver
{
///
/// Resolves the clean username for a given UserId GUID via in-memory cache and FinlyticBackend MQTT RPC.
///
/// The unique ID of the user owning the trade.
/// Cancellation token.
/// The resolved username (e.g. "lars", "kleidukos", "admin") for ntfy channel addressing.
Task ResolveUsernameByUserIdAsync(Guid userId, CancellationToken cancellationToken = default);
}
///
/// Implementation of performing cached MQTT RPC calls to FinlyticBackend
/// without any direct cross-database dependencies.
///
public class UserTradeResolver : IUserTradeResolver
{
private readonly IServiceProvider _serviceProvider;
private readonly IMemoryCache _cache;
private readonly ISettingsService _settingsService;
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private static readonly Regex InvalidChannelCharRegex = new("[^a-zA-Z0-9_-]", RegexOptions.Compiled);
///
/// Initializes a new instance of the class.
///
public UserTradeResolver(
IServiceProvider serviceProvider,
IMemoryCache cache,
ISettingsService settingsService,
IConfiguration configuration,
ILogger logger)
{
_serviceProvider = serviceProvider;
_cache = cache;
_settingsService = settingsService;
_configuration = configuration;
_logger = logger;
}
///
public async Task ResolveUsernameByUserIdAsync(Guid userId, CancellationToken cancellationToken = default)
{
string defaultUsername = await _settingsService.GetSettingAsync(NotifySettingKeys.NtfyDefaultUsername, cancellationToken);
if (string.IsNullOrWhiteSpace(defaultUsername))
{
defaultUsername = _configuration.GetValue("Ntfy:DefaultUsername") ?? "admin";
}
if (userId == Guid.Empty)
{
return defaultUsername;
}
string cacheKey = $"user_name_{userId}";
if (_cache.TryGetValue(cacheKey, out string? cachedUsername) && !string.IsNullOrWhiteSpace(cachedUsername))
{
return cachedUsername;
}
try
{
var mqttClient = _serviceProvider.GetService();
if (mqttClient != null && mqttClient.IsConnected)
{
var username = await mqttClient.SendRpcRequestAsync(
MqttTopics.Channels.BackendGetUsername,
new UserIdRequest(userId),
TimeSpan.FromSeconds(2));
if (!string.IsNullOrWhiteSpace(username))
{
string cleanChannel = CleanUsername(username);
_cache.Set(cacheKey, cleanChannel, TimeSpan.FromHours(1));
return cleanChannel;
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "[UserTradeResolver] Failed to resolve username from FinlyticBackend for UserId {UserId}. Falling back to default.", userId);
}
return defaultUsername;
}
private static string CleanUsername(string rawName)
{
var cleaned = rawName.Trim().ToLowerInvariant().Replace(" ", "_");
cleaned = InvalidChannelCharRegex.Replace(cleaned, "");
return string.IsNullOrWhiteSpace(cleaned) ? "admin" : cleaned;
}
}