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

namespace ZB.MOM.WW.ScadaBridge.ConfigurationDatabase.Entities;

/// <summary>
/// EF Core persistence shape for the central <c>dbo.AuditLog</c> table after the
/// schema collapse. The table is now the 10 canonical
/// <c>ZB.MOM.WW.Audit.AuditEvent</c> fields stored DIRECTLY plus a set of
/// read-only, server-side <b>persisted computed columns</b> derived from
/// <see cref="DetailsJson"/> (<c>JSON_VALUE</c> … <c>PERSISTED</c>) so the
/// reporting queries stay indexable without re-parsing JSON.
/// </summary>
/// <remarks>
/// <para>
/// The transitional 24-typed-column shim is retired. The
/// repository writes the 10 canonical columns directly (no <c>Decompose</c>) and
/// the computed columns auto-derive at INSERT; reads build the canonical
/// <c>AuditEvent</c> straight off the canonical columns (no <c>Recompose</c>).
/// </para>
/// <para>
/// <b>Canonical columns (writable):</b> <see cref="EventId"/>,
/// <see cref="OccurredAtUtc"/>, <see cref="Actor"/>, <see cref="Action"/>,
/// <see cref="Outcome"/>, <see cref="Channel"/> (the canonical <c>Category</c>
/// column — for ScadaBridge, Category = channel name), <see cref="Target"/>,
/// <see cref="SourceNode"/>, <see cref="CorrelationId"/>,
/// <see cref="DetailsJson"/>.
/// </para>
/// <para>
/// <b>Persisted computed columns (read-only):</b> <see cref="Kind"/>,
/// <see cref="Status"/>, <see cref="SourceSiteId"/>, <see cref="ExecutionId"/>,
/// <see cref="ParentExecutionId"/> (the five spec'd queryability columns), plus
/// <see cref="IngestedAtUtc"/> (central ingest timestamp, also a DetailsJson
/// field). These are populated by SQL Server from <see cref="DetailsJson"/>; EF
/// never writes them. Their getters expose them as typed
/// (enum / <see cref="Guid"/> / <see cref="DateTime"/>) properties so the
/// existing LINQ filter/aggregate queries keep their meaning; the value
/// converters that turn enum names ⇄ varchar match the JSON_VALUE string output.
/// </para>
/// <para>
/// All <c>*Utc</c>-suffixed <see cref="DateTime"/> properties are invariantly UTC
/// (CLAUDE.md: "All timestamps are UTC throughout the system."). The init-setters
/// force <see cref="DateTimeKind.Utc"/> on assignment so a value re-hydrated from a
/// SQL Server <c>datetime2</c> column (which strips the <c>Kind</c> flag on the wire)
/// cannot leak downstream as <see cref="DateTimeKind.Unspecified"/> or be silently
/// re-interpreted as local time.
/// </para>
/// </remarks>
public sealed record AuditLogRow
{
    // ── Canonical columns (the 10 ZB.MOM.WW.Audit.AuditEvent fields) ──────────

    /// <summary>Idempotency key; uniquely identifies one audit lifecycle event.</summary>
    public Guid EventId { get; init; }

    /// <summary>UTC timestamp when the audited action occurred at its source.</summary>
    public DateTime OccurredAtUtc
    {
        get => _occurredAtUtc;
        init => _occurredAtUtc = DateTime.SpecifyKind(value, DateTimeKind.Utc);
    }
    private readonly DateTime _occurredAtUtc;

    /// <summary>Authenticated actor for inbound paths (API key name, user, etc.); null/empty for system/anon.</summary>
    public string? Actor { get; init; }

    /// <summary>Canonical action verb — <c>"{channel}.{kind}"</c> (e.g. <c>ApiOutbound.ApiCall</c>).</summary>
    public string Action { get; init; } = string.Empty;

    /// <summary>Normalized canonical outcome (Success / Failure / Denied).</summary>
    public AuditOutcome Outcome { get; init; }

    /// <summary>
    /// Trust-boundary channel the audited action crossed. Stored in the canonical
    /// <c>Category</c> column (for ScadaBridge the canonical Category IS the channel
    /// name); exposed here as the strongly-typed <see cref="AuditChannel"/> enum.
    /// </summary>
    public AuditChannel Channel { get; init; }

    /// <summary>Target of the action: external system name, db connection name, list name, or inbound method.</summary>
    public string? Target { get; init; }

    /// <summary>The cluster node on which the event was emitted.</summary>
    public string? SourceNode { get; init; }

    /// <summary>Correlation id linking related audit rows (e.g. the cached-op lifecycle).</summary>
    public Guid? CorrelationId { get; init; }

    /// <summary>Canonical JSON extension bag carrying every ScadaBridge domain field.</summary>
    public string? DetailsJson { get; init; }

    // ── Persisted computed columns (read-only; derived from DetailsJson) ──────

    /// <summary>
    /// Specific event kind. Computed column <c>JSON_VALUE(DetailsJson,'$.kind')</c>
    /// PERSISTED; read-only (the DB derives it on INSERT).
    /// </summary>
    public AuditKind Kind { get; init; }

    /// <summary>
    /// Lifecycle status. Computed column <c>JSON_VALUE(DetailsJson,'$.status')</c>
    /// PERSISTED; read-only.
    /// </summary>
    public AuditStatus Status { get; init; }

    /// <summary>
    /// Site id where the action originated; null for central-direct events. Computed
    /// column <c>JSON_VALUE(DetailsJson,'$.sourceSiteId')</c> PERSISTED; read-only.
    /// </summary>
    public string? SourceSiteId { get; init; }

    /// <summary>
    /// Id of the originating script execution / inbound request. Computed column
    /// <c>CAST(JSON_VALUE(DetailsJson,'$.executionId') AS uniqueidentifier)</c>
    /// PERSISTED; read-only.
    /// </summary>
    public Guid? ExecutionId { get; init; }

    /// <summary>
    /// ExecutionId of the execution that spawned this run; null for top-level runs.
    /// Computed column
    /// <c>CAST(JSON_VALUE(DetailsJson,'$.parentExecutionId') AS uniqueidentifier)</c>
    /// PERSISTED; read-only.
    /// </summary>
    public Guid? ParentExecutionId { get; init; }

    /// <summary>
    /// UTC timestamp when the central AuditLog store ingested this row; null until
    /// central stamps it. Computed column over
    /// <c>JSON_VALUE(DetailsJson,'$.ingestedAtUtc')</c> (normalized to UTC datetime2)
    /// PERSISTED; read-only.
    /// </summary>
    public DateTime? IngestedAtUtc
    {
        get => _ingestedAtUtc;
        init => _ingestedAtUtc = value.HasValue
            ? DateTime.SpecifyKind(value.Value, DateTimeKind.Utc)
            : null;
    }
    private readonly DateTime? _ingestedAtUtc;
}
