using AnalyticsDAL.CustomCode.AnalysisObject; using AnalyticsDAL.DTO.Analysis; using GB5Shared.DALCache; using Newtonsoft.Json; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace AnalyticsBLL.AnalysisObject { public class AnalysisObjectBLL : IAnalysisObjectBLL { private readonly IAnalysisObjectDAL _dal; private readonly KeyInvalidate _keyInvalidate; private readonly IDALCache _dalCache; private readonly ILogger _logger; private readonly CacheKeyGeneration _keyGen = new(); public AnalysisObjectBLL( IAnalysisObjectDAL dal, KeyInvalidate keyInvalidate, IDALCache dalCache, ILogger logger) { _dal = dal; _keyInvalidate = keyInvalidate; _dalCache = dalCache; _logger = logger; } public async Task GetAnalysisObject(int analysisObjectId, LoginDTO login, CancellationToken ct) => await _dal.GetAnalysisObject(analysisObjectId, login, ct).ConfigureAwait(false); public async Task GetAnalysisObjectsByAnalysis(int analysisId, LoginDTO login, CancellationToken ct) => await _dal.GetAnalysisObjectsByAnalysis(analysisId, login, ct).ConfigureAwait(false); public async Task SaveAnalysisObject(AnalysisObjectDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-analysisobject", new { dto.AnalysisObjectId, dto.AnalysisId }); if (dto.AnalysisId <= 0) throw new ArgumentException("AnalysisId is required."); if (dto.DBObjectId <= 0) throw new ArgumentException("DBObjectId is required."); if (await _dal.IsDuplicate(dto.AnalysisId, dto.DBObjectId, dto.AnalysisObjectId, login, ct).ConfigureAwait(false)) throw new InvalidOperationException( $"DBObjectId {dto.DBObjectId} is already linked to AnalysisId {dto.AnalysisId}."); var isNew = dto.AnalysisObjectId == 0; GB5Trace.Step("save-analysisobject", new { dto.AnalysisObjectId, isNew }); int savedId; if (isNew) { savedId = await _dal.SaveAnalysisObject(dto, login, ct).ConfigureAwait(false); } else { savedId = dto.AnalysisObjectId; await _dal.UpdateAnalysisObject(dto, login, ct).ConfigureAwait(false); } var cacheKey = new CacheKeyGeneration().KeyGeneration( dto.AnalysisId, EntityConstant.OBJECTANALYSIS, CacheKeyLevel.CLIENT_LEVEL, login); var dalKey = _keyGen.KeyGeneration(dto.AnalysisId, EntityConstant.OBJECTANALYSIS, CacheKeyLevel.CLIENT_LEVEL, login); await Task.WhenAll( _keyInvalidate.AllInvalidateCache(cacheKey), _dalCache.InvalidateAsync(dalKey, ct)).ConfigureAwait(false); return isNew ? $"{SuccessResponse.SaveSuccessMessage} {savedId}" : SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("save-analysisobject-failed", ex); _logger.LogError(ex, "SaveAnalysisObject failed for AnalysisId {Id}", dto.AnalysisId); throw; } } public async Task DeleteAnalysisObject(int analysisObjectId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-analysisobject", new { analysisObjectId }); // Fetch AnalysisId BEFORE deleting — the cache is keyed by AnalysisId (the parent), // not by AnalysisObjectId. Invalidating with the wrong ID leaves a stale cache entry. var raw = await _dal.GetAnalysisObject(analysisObjectId, login, ct).ConfigureAwait(false); var existing = Newtonsoft.Json.JsonConvert.DeserializeObject(raw); await _dal.DeleteAnalysisObject(analysisObjectId, login, ct).ConfigureAwait(false); if (existing != null && existing.AnalysisId > 0) { var cacheKey = _keyGen.KeyGeneration(existing.AnalysisId, EntityConstant.OBJECTANALYSIS, CacheKeyLevel.CLIENT_LEVEL, login); var dalKey = _keyGen.KeyGeneration(existing.AnalysisId, EntityConstant.OBJECTANALYSIS, CacheKeyLevel.CLIENT_LEVEL, login); await Task.WhenAll( _keyInvalidate.AllInvalidateCache(cacheKey), _dalCache.InvalidateAsync(dalKey, ct)).ConfigureAwait(false); } return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-analysisobject-failed", ex); _logger.LogError(ex, "DeleteAnalysisObject failed for AnalysisObjectId {Id}", analysisObjectId); throw; } } } }