#nullable enable
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Baya.Application.Contracts.Audit;
using Baya.Application.Contracts.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Baya.Infrastructure.Persistence.Services.Scheduling.Jobs;
///
/// Retention/archival policy for the append-only ops.AuditLogs table (refinement-phase-9 ยง9.4). The audit
/// trail is never pruned by the application except here, on a schedule, and even then a two-tier policy protects
/// the legally-sensitive rows: financial & verification entity types (refunds, clawbacks, payouts,
/// payout batches, verification decisions, config changes, partner centers) keep a long window
/// (audit_retention_financial_days, default ~7 years); everyday operational rows keep a shorter one
/// (audit_retention_general_days, default ~2 years). Idempotent: a re-run simply finds nothing new to
/// delete. Runs on the audit_retention_scan_cadence_hours cadence.
///
internal sealed class AuditLogRetentionJob(ILogger logger) : IRecurringJob
{
// Bound the per-run working set so a large backlog drains across successive runs without loading the whole table.
private const int MaxRowsPerRun = 20_000;
///
/// The IAuditable entity types whose audit rows carry money- or verification-legal weight. Their
/// EntityType is the CLR type name written by AuditFieldInterceptor. Everything else uses the
/// shorter general window.
///
private static readonly IReadOnlyCollection FinancialEntityTypes =
[
"Refund",
"NurseClawback",
"NursePayout",
"NursePayoutBatch",
"NurseVerification",
"PlatformConfig",
"PartnerCenter"
];
public string Name => "audit_log_retention";
public async ValueTask GetIntervalAsync(IServiceProvider services, CancellationToken cancellationToken)
{
var config = services.GetRequiredService();
var hours = await config.GetConfig("audit_retention_scan_cadence_hours", cancellationToken);
return TimeSpan.FromHours(hours);
}
public async ValueTask RunAsync(IServiceProvider services, CancellationToken cancellationToken)
{
var config = services.GetRequiredService();
var audit = services.GetRequiredService();
var generalDays = await config.GetConfig("audit_retention_general_days", cancellationToken);
var financialDays = await config.GetConfig("audit_retention_financial_days", cancellationToken);
var deleted = await audit.PurgeExpiredAsync(
generalDays, financialDays, FinancialEntityTypes, MaxRowsPerRun, cancellationToken);
if (deleted > 0)
logger.LogInformation(
"Audit retention purged {Count} audit row(s) (general>{GeneralDays}d, financial>{FinancialDays}d)",
deleted, generalDays, financialDays);
}
}