using EntitlementBLL.Exceptions; using EntitlementBLL.Interfaces; using EntitlementDAL.DTOs; using EntitlementDAL.Interfaces; using GB5Shared.DTO.Framework.Login; using GB5Shared.EventLogPublish; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using static GB5Shared.GB5Constant.Constant; namespace EntitlementBLL.Implementations; public class BetaClientBLL : IBetaClientBLL { private readonly IBetaClientDAL _BetaClientDAL; private readonly AutoNumber _AutoNumber; private readonly IQueryExecutor _QueryExecutor; private readonly EventLogPublish _EventLog; public BetaClientBLL(IBetaClientDAL betaClientDAL, AutoNumber autoNumber, IQueryExecutor queryExecutor, EventLogPublish eventLog) { _BetaClientDAL = betaClientDAL; _AutoNumber = autoNumber; _QueryExecutor = queryExecutor; _EventLog = eventLog; } public async Task> GetListAsync(LoginDTO login, CancellationToken ct) => await _BetaClientDAL.GetListAsync(login, ct).ConfigureAwait(false); public async Task> GetListByClientAsync(int clientId, LoginDTO login, CancellationToken ct) => await _BetaClientDAL.GetListByClientAsync(clientId, login, ct).ConfigureAwait(false); public async Task> GetOpenProgramsAsync(LoginDTO login, CancellationToken ct) => await _BetaClientDAL.GetOpenProgramsAsync(DateTime.UtcNow, login, ct).ConfigureAwait(false); public async Task SaveAsync(BetaClientDTO dto, LoginDTO login, CancellationToken ct) { if (dto is null) throw new ArgumentNullException(nameof(dto)); if (string.IsNullOrWhiteSpace(dto.BetaProgramName)) throw new ArgumentException("BetaProgramName is required.", nameof(dto)); var tx = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { GB5Trace.Step("validate-beta-client", new { dto.ClientId, dto.BetaProgramName }); var now = DateTime.UtcNow; var auto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.ENTITLEMENTBETACLIENT, login).ConfigureAwait(false); dto.BetaClientId = auto.StartNumber; dto.CreatedById = login.UserId; dto.CreatedOn = now; dto.ModifiedById = login.UserId; dto.ModifiedOn = now; GB5Trace.Step("save-beta-client", new { dto.BetaClientId }); await _BetaClientDAL.SaveAsync(dto, login, tx, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(tx).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {dto.BetaClientId}"; } catch (Exception ex) { await _QueryExecutor.RollbackAsync(tx).ConfigureAwait(false); GB5Trace.MarkFailed("save-beta-client-failed", ex); throw; } } public async Task DeleteAsync(int betaClientId, LoginDTO login, CancellationToken ct) { var tx = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { GB5Trace.Step("delete-beta-client", new { betaClientId }); await _BetaClientDAL.DeleteAsync(betaClientId, login, tx, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(tx).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { await _QueryExecutor.RollbackAsync(tx).ConfigureAwait(false); GB5Trace.MarkFailed("delete-beta-client-failed", ex); throw; } } public async Task EnrolAsync(int clientId, string betaProgramName, DateTime validFrom, DateTime validUntil, LoginDTO login, CancellationToken ct) { if (string.IsNullOrWhiteSpace(betaProgramName)) throw new ArgumentException("BetaProgramName is required.", nameof(betaProgramName)); GB5Trace.Step("validate-beta-enrol", new { clientId, betaProgramName }); var existing = await _BetaClientDAL.GetByClientAndProgramAsync(clientId, betaProgramName, login, ct).ConfigureAwait(false); var tx = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { if (existing is not null) { // A prior enrolment (Active or opted-out) already occupies the unique key // (ClientId, BetaProgramName) — reactivate it rather than insert a duplicate. GB5Trace.Step("reactivate-beta-client", new { existing.BetaClientId }); await _BetaClientDAL.ReactivateAsync(existing.BetaClientId, validFrom, validUntil, login, tx, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(tx).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.ENTITLEMENTBETAENROLLEDEVENTTYPEID }); await _EventLog.PublishEventLogAsync( "Entitlement Beta Client Enrolled", existing, EventTypeConstant.ENTITLEMENTBETAENROLLEDEVENTTYPEID, existing.BetaClientId, login, ct: ct).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } var now = DateTime.UtcNow; var auto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.ENTITLEMENTBETACLIENT, login).ConfigureAwait(false); var dto = new BetaClientDTO { BetaClientId = auto.StartNumber, ClientId = clientId, BetaProgramName = betaProgramName, ValidFrom = validFrom, ValidUntil = validUntil, CreatedById = login.UserId, CreatedOn = now, ModifiedById = login.UserId, ModifiedOn = now }; GB5Trace.Step("save-beta-enrol", new { dto.BetaClientId }); await _BetaClientDAL.SaveAsync(dto, login, tx, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(tx).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.ENTITLEMENTBETAENROLLEDEVENTTYPEID }); await _EventLog.PublishEventLogAsync( "Entitlement Beta Client Enrolled", dto, EventTypeConstant.ENTITLEMENTBETAENROLLEDEVENTTYPEID, dto.BetaClientId, login, ct: ct).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {dto.BetaClientId}"; } catch (Exception ex) { await _QueryExecutor.RollbackAsync(tx).ConfigureAwait(false); GB5Trace.MarkFailed("beta-enrol-failed", ex); throw; } } public async Task OptOutAsync(int clientId, string betaProgramName, LoginDTO login, CancellationToken ct) { GB5Trace.Step("validate-beta-optout", new { clientId, betaProgramName }); var existing = await _BetaClientDAL.GetByClientAndProgramAsync(clientId, betaProgramName, login, ct).ConfigureAwait(false) ?? throw new EntitlementNotFoundException($"Client {clientId} is not enrolled in beta program '{betaProgramName}'."); var tx = await _QueryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { GB5Trace.Step("delete-beta-client", new { existing.BetaClientId }); await _BetaClientDAL.DeleteAsync(existing.BetaClientId, login, tx, ct).ConfigureAwait(false); await _QueryExecutor.CommitAsync(tx).ConfigureAwait(false); GB5Trace.Step("event-publish", new { EventTypeConstant.ENTITLEMENTBETAOPTEDOUTEVENTTYPEID }); await _EventLog.PublishEventLogAsync( "Entitlement Beta Client Opted Out", existing, EventTypeConstant.ENTITLEMENTBETAOPTEDOUTEVENTTYPEID, existing.BetaClientId, login, ct: ct).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { await _QueryExecutor.RollbackAsync(tx).ConfigureAwait(false); GB5Trace.MarkFailed("beta-optout-failed", ex); throw; } } }