using System.Linq;
using EntitlementDAL.DTOs;
using EntitlementDAL.Interfaces;
using GB5Shared.DTO.Framework.Login;
using GB5Shared.GenerateAutoNumber;
using static GB5Shared.GB5Constant.Constant;
namespace EntitlementBLL.Legal;
/// Phase 0's interim implementation of IAgreementConsentProvider — backed by
/// Entitlement's own MAGREEMENTTYPE/MAGREEMENTVERSION/LAGREEMENTACCEPTANCE tables (tracker
/// §50/§51). Swapped for a real ClmAgreementConsentClient once the full "GoodBooks CLM" product
/// ships its own future session — every call site using IAgreementConsentProvider needs no
/// change when that swap happens.
public class LocalAgreementConsentProvider : IAgreementConsentProvider
{
private readonly IAgreementDAL _AgreementDAL;
private readonly AutoNumber _AutoNumber;
public LocalAgreementConsentProvider(IAgreementDAL agreementDAL, AutoNumber autoNumber)
{
_AgreementDAL = agreementDAL;
_AutoNumber = autoNumber;
}
public async Task GetApplicableAgreementsAsync(
string jurisdictionCode, string[] agreementTypeCodes, LoginDTO login, CancellationToken ct)
{
if (agreementTypeCodes is null || agreementTypeCodes.Length == 0)
throw new ArgumentException("At least one AgreementTypeCode is required.", nameof(agreementTypeCodes));
var versions = await _AgreementDAL.GetApplicableVersionsAsync(agreementTypeCodes, jurisdictionCode ?? string.Empty, login, ct)
.ConfigureAwait(false);
return new AgreementBundleDTO { Versions = versions };
}
public async Task GetPendingIndividualAgreementVersionIdsAsync(
AgreementSubjectType subjectType, int subjectId, string jurisdictionCode, LoginDTO login, CancellationToken ct)
{
var required = await _AgreementDAL.GetRequiredIndividualVersionsAsync(jurisdictionCode ?? string.Empty, login, ct).ConfigureAwait(false);
if (required.Count == 0) return Array.Empty();
var accepted = await _AgreementDAL.GetAcceptancesBySubjectAsync((byte)subjectType, subjectId, login, ct).ConfigureAwait(false);
var acceptedVersionIds = accepted.Select(a => a.AgreementVersionId).ToHashSet();
return required
.Where(v => !acceptedVersionIds.Contains(v.AgreementVersionId))
.Select(v => v.AgreementVersionId)
.ToArray();
}
public async Task RecordAcceptanceAsync(
AgreementSubjectType subjectType, int subjectId, int[] agreementVersionIds,
string? ipAddress, string? userAgent, AgreementAcceptanceMethod method,
string correlationKey, LoginDTO login, CancellationToken ct)
{
if (agreementVersionIds is null || agreementVersionIds.Length == 0)
throw new ArgumentException("At least one AgreementVersionId is required.", nameof(agreementVersionIds));
if (string.IsNullOrWhiteSpace(correlationKey))
throw new ArgumentException("CorrelationKey is required.", nameof(correlationKey));
var now = DateTime.UtcNow;
// Re-validate each version is still Published before writing — a version can go stale
// between page-load and submit (the exact same re-validation discipline this engagement
// already applies to a rollout/change-request's own eligibility checks, e.g. §40's Gate
// A/B re-check at delivery time, not just at target-resolution time).
foreach (var agreementVersionId in agreementVersionIds)
{
var version = await _AgreementDAL.GetVersionByIdAsync(agreementVersionId, login, ct).ConfigureAwait(false)
?? throw new InvalidOperationException($"AgreementVersionId {agreementVersionId} not found.");
if (version.VersionStatus != (byte)AgreementVersionStatus.Published)
throw new InvalidOperationException(
$"AgreementVersionId {agreementVersionId} is no longer Published (current status: {(AgreementVersionStatus)version.VersionStatus}) — refresh the bundle and ask the subject to re-accept.");
}
foreach (var agreementVersionId in agreementVersionIds)
{
var auto = await _AutoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.ENTITLEMENTAGREEMENTACCEPTANCE, login).ConfigureAwait(false);
var dto = new AgreementAcceptanceDTO
{
AgreementAcceptanceId = auto.StartNumber,
SubjectType = (byte)subjectType,
SubjectId = subjectId,
AgreementVersionId = agreementVersionId,
AcceptedOn = now,
IpAddress = ipAddress,
UserAgent = userAgent,
AcceptanceMethod = (byte)method,
CorrelationKey = correlationKey,
CreatedById = login.UserId,
CreatedOn = now,
TenantId = login.ClientId,
};
await _AgreementDAL.InsertAcceptanceAsync(dto, login, ct).ConfigureAwait(false);
}
}
}