using ZB.MOM.WW.Audit;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;

namespace ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;

/// <summary>
/// Single construction point for the canonical <see cref="ZB.MOM.WW.Audit.AuditEvent"/>
/// from ScadaBridge's domain vocabulary. Every emit site builds its row through
/// <see cref="Create"/> so the canonical-field mapping (Channel/Kind/Status →
/// Action/Category/Outcome, every other domain field → <see cref="AuditDetails"/>
/// inside <see cref="ZB.MOM.WW.Audit.AuditEvent.DetailsJson"/>) is applied
/// identically everywhere — no per-site drift.
/// </summary>
/// <remarks>
/// <para>The canonical
/// record is the type at every seam, emit site, DTO boundary, and redactor; the
/// ScadaBridge domain fields ride in <c>DetailsJson</c> via
/// <see cref="AuditDetailsCodec"/>.</para>
/// <para>Mapping:
/// <list type="bullet">
///   <item><c>Action</c> = <see cref="AuditFieldBuilders.BuildAction"/>(channel, kind).</item>
///   <item><c>Category</c> = <see cref="AuditFieldBuilders.BuildCategory"/>(channel) (= channel name).</item>
///   <item><c>Outcome</c> = <see cref="AuditOutcomeProjector.Project"/>(status, kind).</item>
///   <item><c>DetailsJson</c> carries Channel/Kind/Status (as strings) + every other
///     ScadaBridge domain field. <c>ForwardState</c> is NOT a DetailsJson field — it is
///     a site-storage-only concern handled by the site SQLite shim.</item>
/// </list>
/// </para>
/// </remarks>
public static class ScadaBridgeAuditEventFactory
{
    /// <summary>
    /// Builds the canonical <see cref="ZB.MOM.WW.Audit.AuditEvent"/> for one ScadaBridge
    /// audit row. <paramref name="channel"/>/<paramref name="kind"/>/<paramref name="status"/>
    /// drive the canonical Action/Category/Outcome and are also recorded (as strings) in
    /// <c>DetailsJson</c>; all remaining ScadaBridge domain fields are carried in
    /// <c>DetailsJson</c> too.
    /// </summary>
    /// <param name="channel">Trust-boundary channel the audited action crossed.</param>
    /// <param name="kind">Specific event kind within the channel.</param>
    /// <param name="status">Lifecycle status of this row.</param>
    /// <param name="eventId">Idempotency key. Defaults to a fresh <see cref="Guid"/> when omitted.</param>
    /// <param name="occurredAtUtc">When the action occurred (UTC). Defaults to <see cref="DateTime.UtcNow"/> when omitted.</param>
    /// <param name="actor">Authenticated actor for inbound paths (API key name, user, "system", etc.).</param>
    /// <param name="target">Target of the action (external system, db connection, list name, inbound method).</param>
    /// <param name="sourceNode">Cluster node that emitted the event (top-level canonical field).</param>
    /// <param name="correlationId">Per-operation lifecycle correlation id (top-level canonical field).</param>
    /// <param name="executionId">Originating script-execution / inbound-request id (DetailsJson).</param>
    /// <param name="parentExecutionId">Spawning execution's id (DetailsJson).</param>
    /// <param name="sourceSiteId">Site id where the action originated (DetailsJson).</param>
    /// <param name="sourceInstanceId">Instance id where the action originated (DetailsJson).</param>
    /// <param name="sourceScript">Script that initiated the action (DetailsJson).</param>
    /// <param name="httpStatus">HTTP status code where applicable (DetailsJson).</param>
    /// <param name="durationMs">Duration of the audited action in ms (DetailsJson).</param>
    /// <param name="errorMessage">Human-readable error summary on failure rows (DetailsJson).</param>
    /// <param name="errorDetail">Verbose error detail (stack/exception) on failure rows (DetailsJson).</param>
    /// <param name="requestSummary">Truncated/redacted request summary (DetailsJson).</param>
    /// <param name="responseSummary">Truncated/redacted response summary (DetailsJson).</param>
    /// <param name="payloadTruncated">True when summaries were truncated to the payload cap (DetailsJson).</param>
    /// <param name="extra">Free-form JSON extension for channel-specific extras (DetailsJson).</param>
    /// <param name="ingestedAtUtc">UTC ingest timestamp (central-set; DetailsJson).</param>
    /// <returns>A fully-populated <see cref="AuditEvent"/> with the top-level fields and serialized <c>DetailsJson</c> set.</returns>
    public static AuditEvent Create(
        AuditChannel channel,
        AuditKind kind,
        AuditStatus status,
        Guid? eventId = null,
        DateTime? occurredAtUtc = null,
        string? actor = null,
        string? target = null,
        string? sourceNode = null,
        Guid? correlationId = null,
        Guid? executionId = null,
        Guid? parentExecutionId = null,
        string? sourceSiteId = null,
        string? sourceInstanceId = null,
        string? sourceScript = null,
        int? httpStatus = null,
        int? durationMs = null,
        string? errorMessage = null,
        string? errorDetail = null,
        string? requestSummary = null,
        string? responseSummary = null,
        bool payloadTruncated = false,
        string? extra = null,
        DateTimeOffset? ingestedAtUtc = null)
    {
        var details = new AuditDetails
        {
            Channel = channel.ToString(),
            Kind = kind.ToString(),
            Status = status.ToString(),
            ExecutionId = executionId,
            ParentExecutionId = parentExecutionId,
            SourceSiteId = sourceSiteId,
            SourceInstanceId = sourceInstanceId,
            SourceScript = sourceScript,
            HttpStatus = httpStatus,
            DurationMs = durationMs,
            ErrorMessage = errorMessage,
            ErrorDetail = errorDetail,
            RequestSummary = requestSummary,
            ResponseSummary = responseSummary,
            PayloadTruncated = payloadTruncated,
            Extra = extra,
            IngestedAtUtc = ingestedAtUtc,
        };

        return new AuditEvent
        {
            EventId = eventId ?? Guid.NewGuid(),
            // DateTimeOffset assumes UTC when the source DateTime is Unspecified/Utc;
            // every ScadaBridge OccurredAt value is UTC by contract.
            OccurredAtUtc = new DateTimeOffset(
                DateTime.SpecifyKind(occurredAtUtc ?? DateTime.UtcNow, DateTimeKind.Utc)),
            Actor = actor ?? string.Empty,
            Action = AuditFieldBuilders.BuildAction(channel, kind),
            Category = AuditFieldBuilders.BuildCategory(channel),
            Outcome = AuditOutcomeProjector.Project(status, kind),
            Target = target,
            SourceNode = sourceNode,
            CorrelationId = correlationId,
            DetailsJson = AuditDetailsCodec.Serialize(details),
        };
    }
}
