using ZB.MOM.WW.Configuration;

namespace ZB.MOM.WW.ScadaBridge.HealthMonitoring;

/// <summary>
/// Validates <see cref="HealthMonitoringOptions"/> at
/// startup. The interval values are fed straight into <c>new PeriodicTimer(...)</c>
/// (and into a division for the offline-check cadence); a zero or negative value
/// makes <see cref="PeriodicTimer"/>'s constructor throw
/// <see cref="ArgumentOutOfRangeException"/>, crashing the
/// <see cref="HealthReportSender"/> / <see cref="CentralHealthReportLoop"/> /
/// <see cref="CentralHealthAggregator"/> hosted service with an opaque exception
/// that does not name the offending config key. Registered with
/// <c>ValidateOnStart()</c> so a bad <c>ScadaBridge:HealthMonitoring</c> section
/// fails fast at boot with a clear, key-naming message.
/// </summary>
public sealed class HealthMonitoringOptionsValidator : OptionsValidatorBase<HealthMonitoringOptions>
{
    /// <inheritdoc />
    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.");
    }
}
