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

namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring;

/// <summary>
/// In-memory state for a single site's health, stored by the central aggregator.
/// Immutable: every state transition produces a new instance which the aggregator
/// installs into its <c>ConcurrentDictionary</c> via an atomic compare-and-swap.
/// This makes handing the reference straight to UI callers safe — a consumer can
/// never observe a torn or half-applied update.
/// </summary>
public sealed record SiteHealthState
{
    /// <summary>Gets the unique identifier of the site this state record belongs to.</summary>
    public required string SiteId { get; init; }

    /// <summary>
    /// The latest full <see cref="SiteHealthReport"/> received for the site, or
    /// <c>null</c> if the site is known only via heartbeats and has not yet sent
    /// a report.
    /// </summary>
    public SiteHealthReport? LatestReport { get; init; }

    /// <summary>
    /// Time the latest full <see cref="SiteHealthReport"/> was processed, or
    /// <c>null</c> if the site is known only via heartbeats and has not yet sent
    /// a report. Used by the UI to surface report staleness during failover;
    /// the <c>null</c> case must be rendered as "no report yet" rather than as a
    /// timestamp (a <c>default</c> sentinel would display as year-0001).
    /// </summary>
    public DateTimeOffset? LastReportReceivedAt { get; init; }

    /// <summary>
    /// Time the most recent signal of any kind (full report OR heartbeat) was
    /// received. Drives offline detection — heartbeats from the standby keep the
    /// site marked online even when the active node is unable to produce a report
    /// (mid-failover, brief stalls). Heartbeat cadence is owned by the Cluster
    /// Infrastructure / SiteCommunicationActor (every
    /// CommunicationOptions.TransportHeartbeatInterval — 5s by default).
    /// </summary>
    public DateTimeOffset LastHeartbeatAt { get; init; }

    /// <summary>
    /// Gets the instant this site was first observed by the aggregator (via a
    /// report or a heartbeat); <c>null</c> only for states not built by the
    /// aggregator (hand-constructed fixtures). Anchors metrics-staleness for a
    /// site that has NEVER delivered a report (review 01 round-2 N3): with
    /// <see cref="LastReportReceivedAt"/> null the staleness check previously
    /// skipped the site forever. <see cref="LastHeartbeatAt"/> cannot anchor
    /// this — every heartbeat advances it.
    /// </summary>
    public DateTimeOffset? FirstSeenAt { get; init; }

    /// <summary>Gets the sequence number of the last accepted health report, used to reject out-of-order duplicates.</summary>
    public long LastSequenceNumber { get; init; }
    /// <summary>Gets a value indicating whether the site is currently considered online.</summary>
    public bool IsOnline { get; init; }

    /// <summary>
    /// Gets a value indicating whether the site is online (heartbeats still
    /// arriving) but has sent no full <see cref="SiteHealthReport"/> within
    /// <see cref="HealthMonitoringOptions.MetricsStaleTimeout"/>. This is a signal
    /// <em>distinct</em> from liveness: a site whose HealthReportSender died keeps
    /// heartbeating and would otherwise show "online with frozen metrics forever".
    /// Cleared whenever a fresh report is processed. Heartbeats never set or clear
    /// it — they say nothing about the metrics pipeline.
    /// </summary>
    public bool IsMetricsStale { get; init; }

    /// <summary>
    /// Gets the instant of the last online↔offline flip, or <c>null</c> if the
    /// site has never changed status since it was first observed. Answers "when
    /// did this site drop / come back". Not moved by report/heartbeat traffic that
    /// leaves the online/offline status unchanged, nor by the metrics-stale flag.
    /// </summary>
    public DateTimeOffset? LastStatusChangeAt { get; init; }
}
