using System.Text.Json; using FLSBLL.Dispatch; using FLSDAL.CustomCode.Instance; using FLSDAL.CustomCode.Respondent; using FLSDAL.CustomCode.Session; using FLSDAL.DTO.Respondent; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using GB5Shared.Validation; using Microsoft.Extensions.Logging; namespace FLSBLL.Respondent; public class FlsRespondentBLL : IFlsRespondentBLL { private readonly IFlsRespondentDAL _dal; private readonly IFlsInstanceDAL _instanceDal; private readonly IFlsSessionDAL _sessionDal; private readonly IFlsDispatchBLL _dispatchBll; private readonly AutoNumber _autoNumber; private readonly KeyInvalidate _keyInvalidate; private readonly ILogger _logger; public FlsRespondentBLL( IFlsRespondentDAL dal, IFlsInstanceDAL instanceDal, IFlsSessionDAL sessionDal, IFlsDispatchBLL dispatchBll, AutoNumber autoNumber, KeyInvalidate keyInvalidate, ILogger logger) { _dal = dal; _instanceDal = instanceDal; _sessionDal = sessionDal; _dispatchBll = dispatchBll; _autoNumber = autoNumber; _keyInvalidate = keyInvalidate; _logger = logger; } public async Task> AddRespondentsAsync(AddRespondentsRequest req, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-fls-respondents", new { req.FlsInstanceId, req.GroupId }); var instance = await _instanceDal.GetByIdAsync(req.FlsInstanceId, login, ct).ConfigureAwait(false); if (instance is null || instance.InstanceStatus != 2) throw new InvalidOperationException("Instance must be Open to add respondents."); if (!req.Respondents.Any()) throw new ArgumentException("At least one respondent is required."); var autoNum = await _autoNumber.GetNumberAsync(req.Respondents.Count, "FLSRESPONDENT", login).ConfigureAwait(false); var respondentIds = Enumerable.Range(autoNum.StartNumber, req.Respondents.Count).ToList(); GB5Trace.Step("save-fls-respondents", new { req.FlsInstanceId, req.GroupId, count = req.Respondents.Count }); var added = await _dal.AddRespondentsAsync( req.FlsInstanceId, req.GroupId, req.StepNo, req.Respondents, respondentIds, login, ct).ConfigureAwait(false); // Dispatch EIP for newly added respondents await _dispatchBll.DispatchInitialAsync(req.FlsInstanceId, req.GroupId, login, ct).ConfigureAwait(false); return added; } catch (Exception ex) { GB5Trace.MarkFailed("save-fls-respondents-failed", ex); _logger.LogError(ex, "AddRespondents failed for instance {Id}", req.FlsInstanceId); throw; } } public async Task GetByTokenAsync(Guid accessToken, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-fls-respondent-token", new { accessToken }); var row = await _dal.GetByTokenAsync(accessToken, new LoginDTO { ClientId = 0, UserId = -1 }, ct).ConfigureAwait(false); if (row is null) return "{}"; if (row.TokenExpiry.HasValue && row.TokenExpiry < DateTime.UtcNow) return JsonSerializer.Serialize(new { Error = "LINK_EXPIRED" }); return JsonSerializer.Serialize(row); } catch (Exception ex) { GB5Trace.MarkFailed("get-fls-respondent-token-failed", ex); _logger.LogError(ex, "GetByToken failed"); throw; } } public async Task GetRespondentsAsync( int flsInstanceId, int? groupId, int? statusFilter, int pageNo, int pageSize, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("get-fls-respondents", new { flsInstanceId, groupId, statusFilter, pageNo }); var rows = await _dal.GetByInstanceAsync(flsInstanceId, groupId, statusFilter, pageNo, pageSize, login, ct).ConfigureAwait(false); var total = await _dal.GetByInstanceCountAsync(flsInstanceId, groupId, statusFilter, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(new { TotalCount = total, PageNo = pageNo, PageSize = pageSize, Data = rows }); } catch (Exception ex) { GB5Trace.MarkFailed("get-fls-respondents-failed", ex); _logger.LogError(ex, "GetRespondents failed for instance {Id}", flsInstanceId); throw; } } public async Task GetPendingFormsAsync(int respondentType, int respondentRefId, LoginDTO login, CancellationToken ct) { try { var rows = await _dal.GetPendingFormsAsync(respondentType, respondentRefId, login, ct).ConfigureAwait(false); return JsonSerializer.Serialize(rows); } catch (Exception ex) { GB5Trace.MarkFailed("get-pending-forms-failed", ex); _logger.LogError(ex, "GetPendingForms failed for ref {RefId}", respondentRefId); throw; } } public async Task NudgeRespondentAsync(int flsRespondentId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("nudge-fls-respondent", new { flsRespondentId }); await _dal.NudgeAsync(flsRespondentId, login, ct).ConfigureAwait(false); await _dispatchBll.DispatchNudgeAsync(flsRespondentId, login, ct).ConfigureAwait(false); return SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("nudge-fls-respondent-failed", ex); _logger.LogError(ex, "NudgeRespondent failed for id {Id}", flsRespondentId); throw; } } public async Task OptOutAsync(int flsRespondentId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("optout-fls-respondent", new { flsRespondentId }); await _dal.OptOutAsync(flsRespondentId, login, ct).ConfigureAwait(false); var respondent = await _dal.GetByIdAsync(flsRespondentId, login, ct).ConfigureAwait(false); if (respondent is not null) { try { await _sessionDal.LogResponseAuditAsync( flsSessionId: 0, flsRespondentId, respondent.FlsInstanceId, auditAction: 5, ipAddress: null, userAgent: null, flsRespondentId, login, ct).ConfigureAwait(false); } catch (Exception auditEx) { _logger.LogError(auditEx, "LogResponseAudit failed for respondent {Id}, action OptedOut", flsRespondentId); } } return SuccessResponse.UpdateSuccess; } catch (Exception ex) { GB5Trace.MarkFailed("optout-fls-respondent-failed", ex); _logger.LogError(ex, "OptOut failed for id {Id}", flsRespondentId); throw; } } }