using ZB.MOM.WW.Audit;

namespace ZB.MOM.WW.MxGateway.Server.Security.Audit;

/// <summary>
/// Best-effort, <em>synchronous</em> <see cref="IAuditWriter"/> over the MxGateway-owned
/// <see cref="IAuditEventSink"/>. It honours the canonical
/// <see cref="IAuditWriter"/> contract: a failed audit write is swallowed and logged
/// rather than propagated, so it can never abort the user-facing action that produced it.
/// </summary>
/// <remarks>
/// This is the durable bottom of the audit pipeline. Callers reach it two ways: through
/// <see cref="ChannelAuditWriter"/> — the registered <see cref="IAuditWriter"/>, which
/// enqueues and lets <see cref="AuditDrainService"/> batch events onto the sink — and
/// directly, when there is no drain to batch behind (the <c>apikey</c> CLI, and any host
/// shutdown window), where writing through immediately is the only way the event survives.
/// The best-effort wrapping here also closes the gap that the library's
/// <c>SqliteApiKeyAuditStore.AppendAsync</c> propagated exceptions.
/// </remarks>
public sealed class CanonicalAuditWriter(
    IAuditEventSink sink,
    ILogger<CanonicalAuditWriter> logger) : IAuditWriter
{
    /// <summary>
    /// Persists a canonical audit event to the underlying <see cref="IAuditEventSink"/>.
    /// Any failure is caught, logged, and swallowed rather than propagated to the caller.
    /// </summary>
    /// <param name="auditEvent">The canonical audit event to persist.</param>
    /// <param name="cancellationToken">Token to cancel the underlying store write.</param>
    /// <returns>A task that represents the asynchronous operation.</returns>
    public async Task WriteAsync(AuditEvent auditEvent, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(auditEvent);

        try
        {
            await sink.InsertAsync(auditEvent, cancellationToken).ConfigureAwait(false);
        }
        catch (Exception exception)
        {
            // Best-effort: a failed audit write must never abort the action that produced it.
            // Swallow everything (including OperationCanceledException) and log for diagnosis.
            logger.LogWarning(
                exception,
                "Failed to persist audit event {EventId} (action {Action}); audit write is best-effort and was suppressed.",
                auditEvent.EventId,
                auditEvent.Action);
        }
    }
}
