using ZB.MOM.WW.Configuration;

namespace ZB.MOM.WW.ScadaBridge.KpiHistory;

/// <summary>
/// Validates <see cref="KpiHistoryOptions"/> on startup. A non-positive
/// <see cref="KpiHistoryOptions.SampleInterval"/> or
/// <see cref="KpiHistoryOptions.PurgeInterval"/> would stall the recorder /
/// purge loops, so both are required to be strictly positive.
/// <see cref="KpiHistoryOptions.RetentionDays"/> is bounded to <c>[1, 3650]</c>
/// to keep the purge window sane (too short drops trends still being viewed;
/// too long defeats the retention purpose). <see cref="KpiHistoryOptions.DefaultMaxSeriesPoints"/>
/// is bounded to <c>[2, 5000]</c> — at least two points are needed to draw a
/// line, and an unbounded ceiling would let a single trend query stream an
/// arbitrarily large series to the Central UI.
/// </summary>
public sealed class KpiHistoryOptionsValidator : OptionsValidatorBase<KpiHistoryOptions>
{
    /// <summary>Inclusive lower bound for <see cref="KpiHistoryOptions.RetentionDays"/>.</summary>
    public const int MinRetentionDays = 1;

    /// <summary>Inclusive upper bound for <see cref="KpiHistoryOptions.RetentionDays"/>.</summary>
    public const int MaxRetentionDays = 3650;

    /// <summary>Inclusive lower bound for <see cref="KpiHistoryOptions.DefaultMaxSeriesPoints"/>.</summary>
    public const int MinDefaultMaxSeriesPoints = 2;

    /// <summary>Inclusive upper bound for <see cref="KpiHistoryOptions.DefaultMaxSeriesPoints"/>.</summary>
    public const int MaxDefaultMaxSeriesPoints = 5000;

    /// <summary>Inclusive lower bound for <see cref="KpiHistoryOptions.RollupLookbackHours"/>.</summary>
    public const int MinRollupLookbackHours = 1;

    /// <summary>Inclusive upper bound for <see cref="KpiHistoryOptions.RollupLookbackHours"/> (a week).</summary>
    public const int MaxRollupLookbackHours = 168;

    /// <summary>Inclusive lower bound for <see cref="KpiHistoryOptions.RollupRetentionDays"/>.</summary>
    public const int MinRollupRetentionDays = 1;

    /// <summary>Inclusive upper bound for <see cref="KpiHistoryOptions.RollupRetentionDays"/>.</summary>
    public const int MaxRollupRetentionDays = 3650;

    /// <summary>Inclusive lower bound for <see cref="KpiHistoryOptions.RollupThresholdHours"/> (one day).</summary>
    public const int MinRollupThresholdHours = 24;

    /// <inheritdoc />
    protected override void Validate(ValidationBuilder builder, KpiHistoryOptions options)
    {
        builder.RequireThat(options.SampleInterval > TimeSpan.Zero,
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.SampleInterval)} ({options.SampleInterval}) " +
            "must be > 0; it is the recorder's sampling cadence.");

        // Valid when RetentionDays is within [Min, Max] inclusive. The De Morgan'd
        // guard !(below Min OR above Max) is equivalent to (>= Min AND <= Max).
        builder.RequireThat(
            !(options.RetentionDays < MinRetentionDays || options.RetentionDays > MaxRetentionDays),
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RetentionDays)} ({options.RetentionDays}) " +
            $"must be in [{MinRetentionDays}, {MaxRetentionDays}] days.");

        builder.RequireThat(options.PurgeInterval > TimeSpan.Zero,
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.PurgeInterval)} ({options.PurgeInterval}) " +
            "must be > 0; it is the purge-sweep cadence.");

        // Valid when DefaultMaxSeriesPoints is within [Min, Max] inclusive. The
        // De Morgan'd guard !(below Min OR above Max) is equivalent to
        // (>= Min AND <= Max).
        builder.RequireThat(
            !(options.DefaultMaxSeriesPoints < MinDefaultMaxSeriesPoints
              || options.DefaultMaxSeriesPoints > MaxDefaultMaxSeriesPoints),
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.DefaultMaxSeriesPoints)} ({options.DefaultMaxSeriesPoints}) " +
            $"must be in [{MinDefaultMaxSeriesPoints}, {MaxDefaultMaxSeriesPoints}].");

        builder.RequireThat(options.RollupInterval > TimeSpan.Zero,
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RollupInterval)} ({options.RollupInterval}) " +
            "must be > 0; it is the hourly rollup fold cadence.");

        // Valid when RollupLookbackHours is within [Min, Max] inclusive.
        builder.RequireThat(
            !(options.RollupLookbackHours < MinRollupLookbackHours
              || options.RollupLookbackHours > MaxRollupLookbackHours),
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RollupLookbackHours)} ({options.RollupLookbackHours}) " +
            $"must be in [{MinRollupLookbackHours}, {MaxRollupLookbackHours}] hours.");

        // Valid when RollupRetentionDays is within [Min, Max] inclusive.
        builder.RequireThat(
            !(options.RollupRetentionDays < MinRollupRetentionDays
              || options.RollupRetentionDays > MaxRollupRetentionDays),
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RollupRetentionDays)} ({options.RollupRetentionDays}) " +
            $"must be in [{MinRollupRetentionDays}, {MaxRollupRetentionDays}] days.");

        // Coherence: rollups must be retained AT LEAST as long as raw samples, else a
        // long-range chart hits a hole where raw is purged but the rollup was already
        // dropped (or never written). Enforced independently of the range check above.
        builder.RequireThat(
            options.RollupRetentionDays >= options.RetentionDays,
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RollupRetentionDays)} ({options.RollupRetentionDays}) " +
            $"must be >= {nameof(KpiHistoryOptions.RetentionDays)} ({options.RetentionDays}) so rollups outlive " +
            "raw samples and long-range trends have no data hole.");

        builder.RequireThat(options.RollupThresholdHours >= MinRollupThresholdHours,
            $"ScadaBridge:KpiHistory:{nameof(KpiHistoryOptions.RollupThresholdHours)} ({options.RollupThresholdHours}) " +
            $"must be >= {MinRollupThresholdHours} hours; it is the raw-vs-rollup query-routing boundary.");
    }
}
