using ZB.MOM.WW.Configuration;
namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring;
///
/// Validates at
/// startup. The interval values are fed straight into new PeriodicTimer(...)
/// (and into a division for the offline-check cadence); a zero or negative value
/// makes 's constructor throw
/// , crashing the
/// / /
/// hosted service with an opaque exception
/// that does not name the offending config key. Registered with
/// ValidateOnStart() so a bad ScadaBridge:HealthMonitoring section
/// fails fast at boot with a clear, key-naming message.
///
public sealed class HealthMonitoringOptionsValidator : OptionsValidatorBase
{
///
protected override void Validate(ValidationBuilder builder, HealthMonitoringOptions options)
{
builder.RequireThat(options.ReportInterval > TimeSpan.Zero,
$"ScadaBridge:HealthMonitoring:ReportInterval must be a positive duration " +
$"(was {options.ReportInterval}); it is used directly as a PeriodicTimer period.");
builder.RequireThat(options.OfflineTimeout > TimeSpan.Zero,
$"ScadaBridge:HealthMonitoring:OfflineTimeout must be a positive duration " +
$"(was {options.OfflineTimeout}); it drives the offline-check PeriodicTimer cadence.");
builder.RequireThat(options.CentralOfflineTimeout > TimeSpan.Zero,
$"ScadaBridge:HealthMonitoring:CentralOfflineTimeout must be a positive duration " +
$"(was {options.CentralOfflineTimeout}).");
// Valid when CentralOfflineTimeout >= OfflineTimeout (both already
// required to be positive above). The De Morgan'd guard !(both positive
// AND Central < Offline) is true unless BOTH timeouts are positive and
// Central is strictly smaller — so it stays silent when either field is
// non-positive, leaving that failure to the dedicated positive-duration
// checks above rather than double-firing here.
builder.RequireThat(
!(options.OfflineTimeout > TimeSpan.Zero
&& options.CentralOfflineTimeout > TimeSpan.Zero
&& options.CentralOfflineTimeout < options.OfflineTimeout),
$"ScadaBridge:HealthMonitoring:CentralOfflineTimeout ({options.CentralOfflineTimeout}) " +
$"must be >= OfflineTimeout ({options.OfflineTimeout}): the synthetic 'central' site has " +
"no heartbeat source and is fed only by the slower self-report loop, so it needs at " +
"least as much offline grace as a real site.");
builder.RequireThat(options.MetricsStaleTimeout > TimeSpan.Zero,
$"ScadaBridge:HealthMonitoring:MetricsStaleTimeout must be a positive duration " +
$"(was {options.MetricsStaleTimeout}); it is the window an online site may go without " +
"a full report before being flagged metrics-stale.");
// A stale window shorter than one report interval flags every healthy
// site (reports simply can't arrive faster than they are emitted). Same
// De Morgan guard shape as the Central/Offline pair above: stay silent
// when either field is non-positive so the dedicated positive-duration
// checks own that failure rather than double-firing here.
builder.RequireThat(
!(options.ReportInterval > TimeSpan.Zero
&& options.MetricsStaleTimeout > TimeSpan.Zero
&& options.MetricsStaleTimeout < options.ReportInterval),
$"ScadaBridge:HealthMonitoring:MetricsStaleTimeout ({options.MetricsStaleTimeout}) " +
$"must be >= ReportInterval ({options.ReportInterval}): a stale window shorter than one " +
"report interval would flag every healthy site, since reports cannot arrive faster than " +
"they are emitted.");
builder.RequireThat(options.LocalDbBacklogThreshold > 0,
$"ScadaBridge:HealthMonitoring:LocalDbBacklogThreshold must be positive " +
$"(was {options.LocalDbBacklogThreshold}); a zero/negative threshold would badge every " +
"replicated site permanently, since an idle backlog of 0 already sits at or above it.");
builder.RequireThat(options.LocalDbBacklogSustainedFor > TimeSpan.Zero,
$"ScadaBridge:HealthMonitoring:LocalDbBacklogSustainedFor must be a positive duration " +
$"(was {options.LocalDbBacklogSustainedFor}); it is the dwell time a backlog must stay " +
"above the threshold before the site is badged.");
// A dwell shorter than one report interval can be satisfied by a single sample,
// degrading the sustained check back into the bare threshold it exists to replace —
// which would fire on every daily purge burst. Same De Morgan guard shape as the
// pairs above: silent unless BOTH fields are positive and the dwell is too short,
// so the dedicated positive-value checks own that failure.
builder.RequireThat(
!(options.ReportInterval > TimeSpan.Zero
&& options.LocalDbBacklogSustainedFor > TimeSpan.Zero
&& options.LocalDbBacklogSustainedFor < options.ReportInterval),
$"ScadaBridge:HealthMonitoring:LocalDbBacklogSustainedFor ({options.LocalDbBacklogSustainedFor}) " +
$"must be >= ReportInterval ({options.ReportInterval}): a dwell shorter than one report " +
"interval can be satisfied by a single sample, which turns the sustained check back into " +
"a bare threshold and re-introduces the daily purge-burst false positive it exists to avoid.");
}
}