using System.Security.Cryptography; using System.Text; using System.Text.Json; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; using PartnerBLL.Interfaces; using PartnerDAL.CustomCode.PartnerApiKey; using PartnerDAL.DTO.PartnerApiKey; namespace PartnerBLL.Implementations { public class PartnerApiKeyBLL : IPartnerApiKeyBLL { private readonly IPartnerApiKeyDAL _dal; private readonly IValidation _validation; private readonly ILogger _logger; public PartnerApiKeyBLL(IPartnerApiKeyDAL dal, IValidation validation, ILogger logger) { _dal = dal; _validation = validation; _logger = logger; } public async Task GetPartnerApiKeyList(int partnerProductId, LoginDTO loginDTO) { try { GB5Trace.Step("get-api-key-list", new { partnerProductId }); return await _dal.GetPartnerApiKeyList(partnerProductId, loginDTO).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-api-key-list-failed", ex); _logger.LogError(ex, "GetPartnerApiKeyList failed for PartnerProductId {PartnerProductId}", partnerProductId); throw; } } public async Task GetApiKeyByHash(string hashedKey, LoginDTO loginDTO) { try { return await _dal.GetApiKeyByHash(hashedKey, loginDTO).ConfigureAwait(false); } catch (Exception ex) { GB5Trace.MarkFailed("get-api-key-by-hash-failed", ex); _logger.LogError(ex, "GetApiKeyByHash lookup failed"); throw; } } public async Task SavePartnerApiKey(PartnerApiKeyDTO dto, LoginDTO loginDTO) { try { GB5Trace.Step("validate-api-key", new { dto.PartnerProductId }); await _validation.NotEmpty(dto.KeyName, nameof(dto.KeyName)).ConfigureAwait(false); if (dto.PartnerProductId == 0) throw new ArgumentException("PartnerProductId is required."); // Generate cryptographically random raw key (base64url, 43 chars) var rawBytes = RandomNumberGenerator.GetBytes(32); var rawKey = Convert.ToBase64String(rawBytes) .TrimEnd('=') .Replace('+', '-') .Replace('/', '_'); // SHA-256 hex digest stored in DB — raw key never persisted var hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(rawKey)); dto.HashedKey = Convert.ToHexString(hashBytes).ToLowerInvariant(); // Last 4 chars of raw key for UI identification dto.KeyHint = rawKey[^4..]; dto.CreatedById = loginDTO.UserId; dto.ModifiedById = loginDTO.UserId; dto.CreatedOn = DateTime.UtcNow; dto.ModifiedOn = DateTime.UtcNow; GB5Trace.Step("save-api-key", new { dto.PartnerProductId, dto.KeyName }); var apiKeyId = await _dal.SavePartnerApiKey(dto, loginDTO).ConfigureAwait(false); _logger.LogInformation("PartnerApiKey {ApiKeyId} created for PartnerProductId {PartnerProductId} by user {UserId}", apiKeyId, dto.PartnerProductId, loginDTO.UserId); var response = new { ApiKeyId = apiKeyId, KeyName = dto.KeyName, KeyHint = dto.KeyHint, RawKey = rawKey, ExpiresOn = dto.ExpiresOn, Message = $"{SuccessResponse.SaveSuccessMessage} {apiKeyId}" }; return JsonSerializer.Serialize(response); } catch (Exception ex) { GB5Trace.MarkFailed("save-api-key-failed", ex); _logger.LogError(ex, "SavePartnerApiKey failed for PartnerProductId {PartnerProductId}", dto.PartnerProductId); throw; } } public async Task DeletePartnerApiKey(int apiKeyId, LoginDTO loginDTO) { try { GB5Trace.Step("delete-api-key", new { apiKeyId }); if (apiKeyId <= 0) throw new ArgumentException("ApiKeyId is required."); await _dal.DeletePartnerApiKey(apiKeyId, loginDTO.UserId, loginDTO).ConfigureAwait(false); _logger.LogInformation("PartnerApiKey {ApiKeyId} deleted by user {UserId}", apiKeyId, loginDTO.UserId); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-api-key-failed", ex); _logger.LogError(ex, "DeletePartnerApiKey failed for ApiKeyId {ApiKeyId}", apiKeyId); throw; } } public async Task UpdateApiKeyLastUsed(int apiKeyId, LoginDTO loginDTO) { try { await _dal.UpdateApiKeyLastUsed(apiKeyId, loginDTO).ConfigureAwait(false); } catch (Exception ex) { // Non-critical — log but do not bubble up to caller _logger.LogWarning(ex, "UpdateApiKeyLastUsed failed for ApiKeyId {ApiKeyId}", apiKeyId); } } } }