using System.Text.Json;
using ZB.MOM.WW.Audit;

namespace ZB.MOM.WW.OtOpcUa.AdminUI.Audit;

/// <summary>
///     Pure factory that maps a certificate-store action (Trust / Untrust / Delete of a peer
///     certificate) into a canonical <see cref="AuditEvent" /> for persistence via the shared
///     <see cref="IAuditWriter" /> seam. No side effects: callers build the event then hand it to
///     the writer.
/// </summary>
public static class CertAuditEvents
{
    /// <summary>The audit <see cref="AuditEvent.Category" /> stamped onto every certificate event.</summary>
    public const string Category = "Certificate";

    /// <summary>
    ///     Builds a canonical <see cref="AuditEvent" /> describing a certificate-store action.
    /// </summary>
    /// <param name="action">The action verb — e.g. <c>Trust</c>, <c>Untrust</c>, or <c>Delete</c>.
    /// Stamped onto <see cref="AuditEvent.Action" />.</param>
    /// <param name="store">The certificate store the action targeted (carried in the details payload).</param>
    /// <param name="thumbprint">The subject certificate's thumbprint; stamped onto
    /// <see cref="AuditEvent.SourceNode" /> and echoed in the details payload.</param>
    /// <param name="actor">The identity that performed the action; stamped onto
    /// <see cref="AuditEvent.Actor" />.</param>
    /// <param name="success"><see langword="true" /> if the action succeeded (Outcome
    /// <see cref="AuditOutcome.Success" />); otherwise <see langword="false" /> (Outcome
    /// <see cref="AuditOutcome.Failure" />).</param>
    /// <param name="error">On failure, the error text carried in the details payload; ignored on
    /// success (the <c>error</c> field is omitted from the details payload entirely).</param>
    /// <returns>A fully populated <see cref="AuditEvent" /> with a fresh <see cref="AuditEvent.EventId" />
    /// and <see cref="AuditEvent.OccurredAtUtc" /> set to now (UTC).</returns>
    public static AuditEvent Build(
        string action, string store, string thumbprint, string actor, bool success, string? error)
    {
        // On success the error field is omitted entirely (not serialized as null) so the common
        // success-path audit row carries no dead "error" key.
        var detailsJson = JsonSerializer.Serialize(success
            ? (object)new { store, thumbprint }
            : new { store, thumbprint, error });

        return new AuditEvent
        {
            EventId = Guid.NewGuid(),
            OccurredAtUtc = DateTimeOffset.UtcNow,
            Actor = actor,
            Action = action,
            Category = Category,
            SourceNode = thumbprint,
            Outcome = success ? AuditOutcome.Success : AuditOutcome.Failure,
            DetailsJson = detailsJson,
        };
    }
}
