using System.Security.Cryptography; using System.Text; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using KmsDAL.CustomCode.KmGap; using KmsDAL.DTO.KmGap; namespace KmsBLL.KmGap { public class KmGapBLL : IKmGapBLL { private readonly IKmGapDAL _dal; private readonly AutoNumber _autoNumber; public KmGapBLL(IKmGapDAL dal, AutoNumber autoNumber) { _dal = dal; _autoNumber = autoNumber; } public async Task RecordGap(KmRecordGapDTO dto, LoginDTO login, CancellationToken ct) { dto.TenantId = login.ClientId; dto.CreatedById = login.UserId; // Hash for dedup — matches SQL: md5(lower(trim(QUERYTEXT))) dto.QueryHash = ComputeQueryHash(dto.QueryText); if (dto.BestArtifactId <= 0) dto.BestArtifactId = -1; // Get new ID — the MERGE will use it only on INSERT (not on UPDATE) var autoNum = await _autoNumber.GetNumberAsync(1, "TKMGAP", login); await _dal.UpsertKmGap(dto, autoNum.StartNumber, login, ct); } public async Task GetKmGaps(byte gapStatus, int page, int pageSize, LoginDTO login, CancellationToken ct) => await _dal.GetKmGaps(gapStatus, page, pageSize, login, ct); public async Task ResolveGap(KmResolveGapDTO dto, LoginDTO login, CancellationToken ct) { if (dto.NewStatus != 2 && dto.NewStatus != 3) throw new ArgumentException("NewStatus must be 2 (Resolved) or 3 (WontFix)."); if (string.IsNullOrWhiteSpace(dto.ResolutionNotes)) throw new ArgumentException("ResolutionNotes is required."); await _dal.UpdateKmGapStatus(dto, login, ct); return $"{SuccessResponse.UpdateSuccessMessage} — Gap resolved."; } private static string ComputeQueryHash(string query) { var normalised = query.Trim().ToLowerInvariant(); var bytes = MD5.HashData(Encoding.UTF8.GetBytes(normalised)); return Convert.ToHexString(bytes).ToLowerInvariant(); } } }