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

namespace ZB.MOM.WW.ScadaBridge.Commons.Messages.Health;

public record SiteHealthReport(
    string SiteId,
    long SequenceNumber,
    DateTimeOffset ReportTimestamp,
    IReadOnlyDictionary<string, ConnectionHealth> DataConnectionStatuses,
    IReadOnlyDictionary<string, TagResolutionStatus> TagResolutionCounts,
    int ScriptErrorCount,
    int AlarmEvaluationErrorCount,
    IReadOnlyDictionary<string, int> StoreAndForwardBufferDepths,
    int DeadLetterCount,
    int DeployedInstanceCount,
    int EnabledInstanceCount,
    int DisabledInstanceCount,
    string NodeRole = "Unknown",
    string NodeHostname = "",
    IReadOnlyDictionary<string, string>? DataConnectionEndpoints = null,
    IReadOnlyDictionary<string, TagQualityCounts>? DataConnectionTagQuality = null,
    int ParkedMessageCount = 0,
    IReadOnlyList<NodeStatus>? ClusterNodes = null,
    // Per-interval count of FallbackAuditWriter
    // primary failures (SQLite throws routed to the drop-oldest ring). Surfaces
    // a sustained audit-write outage on /monitoring/health. Defaults to 0 so
    // existing producers / tests that don't construct the field stay valid.
    int SiteAuditWriteFailures = 0,
    // Per-interval count of payload-filter
    // redactor over-redactions (header / body / SQL parameter stages all
    // throwing → field replaced with the "<redacted: redactor error>"
    // marker). Surfaces a misconfigured / catastrophic regex on
    // /monitoring/health. Defaults to 0 for back-compat with existing
    // producers and tests that don't construct the field.
    int AuditRedactionFailure = 0,
    // Point-in-time snapshot of the
    // site-local SQLite audit-log queue (pending count, oldest pending row,
    // on-disk bytes). Populated by the site-side SiteAuditBacklogReporter
    // hosted service every 30 s. Defaults to null so existing producers /
    // tests that don't refresh the snapshot stay valid; the central health
    // surface treats null as "no data yet" rather than a zeroed queue.
    SiteAuditBacklogSnapshot? SiteAuditBacklog = null,
    // Site Event Logging: cumulative count of event-log write
    // failures (SQLite error, disk full, bounded-queue overflow drop) since the
    // logger was created. Populated by the site-side SiteEventLogFailureCountReporter
    // hosted service. Point-in-time (not reset on collect) — mirrors the
    // SiteAuditBacklog pattern. Defaults to 0 so existing producers / tests that
    // don't wire the poller stay valid.
    long SiteEventLogWriteFailures = 0,
    // Age (seconds) of the oldest parked
    // store-and-forward message, computed at report time on the site; null when no
    // rows are parked. Parked rows persist until operator action, so this aging
    // signal surfaces a forgotten site's stale backlog (count alone hides age).
    // Additive-only trailing member — defaults to null for producers / tests that
    // don't populate it.
    double? OldestParkedMessageAgeSeconds = null)
{
    // Script-execution-scheduler gauges (S2/UA5), surfaced additively as init
    // properties (rather than positional parameters) so the constructor signature
    // is untouched. Point-in-time, refreshed on the site by ScriptSchedulerStatsReporter;
    // safe defaults for producers / tests that don't populate them.

    /// <summary>Script tasks waiting to run on the dedicated script-execution scheduler.</summary>
    public int ScriptQueueDepth { get; init; }

    /// <summary>Script-execution worker threads currently running a script body.</summary>
    public int ScriptBusyThreads { get; init; }

    /// <summary>
    /// Age (seconds) of the oldest in-flight script across the scheduler's worker
    /// threads, or null when the pool is idle. A large value with all threads busy
    /// indicates a stuck/blocked script holding a dedicated thread.
    /// </summary>
    public double? ScriptOldestBusyAgeSeconds { get; init; }

    /// <summary>
    /// WP3.1: script-execution worker threads the stuck-script watchdog has DETACHED and
    /// replaced — a script wedged past its deadline plus grace in uninterruptible blocking
    /// I/O, so the pool started a fresh thread and the wedged one will exit only when its
    /// body finally returns. Point-in-time, refreshed by <c>ScriptSchedulerStatsReporter</c>.
    /// Zero is the healthy state; a value that climbs and never drains means script bodies
    /// are permanently consuming threads and the pool is being repeatedly rebuilt around them.
    /// </summary>
    public int DetachedScriptThreads { get; init; }

    /// <summary>
    /// WP3.1: per-interval count of script and alarm on-trigger runs SHED because
    /// <c>MaxConcurrentRunsPerScript</c> runs were already in flight for that script. Raw
    /// per-interval count (drained on collect) like <see cref="ScriptErrorCount"/>. A
    /// sustained non-zero value means a trigger is firing faster than its script completes;
    /// the shed itself is the designed back-pressure, not an error.
    /// </summary>
    public int ScriptRunShedCount { get; init; }

    // LocalDb 2-node replication of the consolidated site database (Phase 1).
    // Additive init properties for the same reason as the scheduler gauges above:
    // the positional constructor stays untouched. Refreshed on the site by
    // LocalDbReplicationStatusReporter.

    /// <summary>
    /// Whether a replication sync session is currently running with the peer site node,
    /// or <see langword="null"/> when replication is not wired on this node.
    /// </summary>
    /// <remarks>
    /// Nullable on purpose, and <see langword="false"/> is NOT the same as null.
    /// Replication ships default-OFF, so a site node with no peer configured reports
    /// <see langword="false"/> — that is a healthy, expected state, not an outage. Only
    /// a node that was configured with a peer and is reporting <see langword="false"/>
    /// is degraded, and distinguishing those two cases is the operator's job, not this
    /// field's.
    /// </remarks>
    public bool? LocalDbReplicationConnected { get; init; }

    /// <summary>
    /// Unacked replication oplog entries waiting to reach the peer, or
    /// <see langword="null"/> when unknown.
    /// </summary>
    /// <remarks>
    /// <b>Null means unknown, never zero.</b> The underlying provider returns null when
    /// no backlog source is wired or the poll failed, and that must survive to the
    /// operator: a failed read rendered as "0 backlog" would report a broken replication
    /// pair as perfectly healthy. Collapsing null to 0 anywhere on this path is a bug.
    /// </remarks>
    public long? LocalDbOplogBacklog { get; init; }

    /// <summary>
    /// WP2.6d (arch-review misc — site stream alarm-vs-attribute shared buffer):
    /// cumulative count of alarm state changes dropped from
    /// <c>SiteStreamManager</c>'s dedicated alarm publish queue because it was at
    /// capacity — i.e. the site-wide alarm hand-off itself fell behind, not a
    /// per-subscriber buffer. Point-in-time (not reset on collect), refreshed by
    /// the site-side <c>SiteStreamAlarmDropReporter</c> hosted service. Defaults to
    /// 0 so existing producers/tests that don't populate it stay valid.
    /// </summary>
    public long SiteStreamAlarmDropCount { get; init; }
}

/// <summary>
/// Broadcast wrapper used between central nodes to keep per-node
/// CentralHealthAggregator state in sync. A site delivers each
/// SiteHealthReport to ONE central node; that node re-publishes
/// this wrapper on a DistributedPubSub topic so the peer node's aggregator
/// also processes the report (idempotently — sequence numbers guard against
/// double-counting).
/// </summary>
public record SiteHealthReportReplica(SiteHealthReport Report);
