using ZB.MOM.WW.ScadaBridge.KpiHistory;

namespace ZB.MOM.WW.ScadaBridge.KpiHistory.Tests;

/// <summary>
/// K3 unit tests for <see cref="KpiHistoryOptionsValidator"/>: the default
/// options must validate, and each rule (SampleInterval / RetentionDays /
/// PurgeInterval / DefaultMaxSeriesPoints) must reject its out-of-range values.
/// </summary>
public class KpiHistoryOptionsValidatorTests
{
    private static KpiHistoryOptionsValidator NewValidator() => new();

    [Fact]
    public void Validate_Defaults_Pass()
    {
        var result = NewValidator().Validate(null, new KpiHistoryOptions());
        Assert.True(result.Succeeded);
    }

    // ---------------------------------------------------------------------
    // SampleInterval > TimeSpan.Zero
    // ---------------------------------------------------------------------

    [Fact]
    public void Validate_SampleInterval_Zero_Fails()
    {
        var opts = new KpiHistoryOptions { SampleInterval = TimeSpan.Zero };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.SampleInterval), StringComparison.Ordinal));
    }

    [Fact]
    public void Validate_SampleInterval_Negative_Fails()
    {
        var opts = new KpiHistoryOptions { SampleInterval = TimeSpan.FromSeconds(-1) };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.SampleInterval), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // RetentionDays in [1, 3650]
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(1)]      // min
    [InlineData(90)]     // default
    [InlineData(3650)]   // max
    public void Validate_RetentionDays_InRange_Passes(int value)
    {
        // Keep RollupRetentionDays coherent (>= RetentionDays) so this test isolates the
        // RetentionDays range rule and doesn't trip the rollup-retention coherence rule when
        // RetentionDays is set above the default RollupRetentionDays (365).
        var opts = new KpiHistoryOptions { RetentionDays = value, RollupRetentionDays = value };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(3651)]
    [InlineData(-1)]
    public void Validate_RetentionDays_OutOfRange_Fails(int value)
    {
        var opts = new KpiHistoryOptions { RetentionDays = value };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RetentionDays), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // PurgeInterval > TimeSpan.Zero
    // ---------------------------------------------------------------------

    [Fact]
    public void Validate_PurgeInterval_Zero_Fails()
    {
        var opts = new KpiHistoryOptions { PurgeInterval = TimeSpan.Zero };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.PurgeInterval), StringComparison.Ordinal));
    }

    [Fact]
    public void Validate_PurgeInterval_Negative_Fails()
    {
        var opts = new KpiHistoryOptions { PurgeInterval = TimeSpan.FromHours(-1) };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.PurgeInterval), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // DefaultMaxSeriesPoints in [2, 5000]
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(2)]      // min
    [InlineData(200)]    // default
    [InlineData(5000)]   // max
    public void Validate_DefaultMaxSeriesPoints_InRange_Passes(int value)
    {
        var opts = new KpiHistoryOptions { DefaultMaxSeriesPoints = value };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    [Theory]
    [InlineData(1)]
    [InlineData(5001)]
    [InlineData(0)]
    public void Validate_DefaultMaxSeriesPoints_OutOfRange_Fails(int value)
    {
        var opts = new KpiHistoryOptions { DefaultMaxSeriesPoints = value };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.DefaultMaxSeriesPoints), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // RollupInterval > TimeSpan.Zero
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    public void Validate_RollupInterval_NotPositive_Fails(int hours)
    {
        var opts = new KpiHistoryOptions { RollupInterval = TimeSpan.FromHours(hours) };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RollupInterval), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // RollupLookbackHours in [1, 168]
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(1)]      // min
    [InlineData(3)]      // default
    [InlineData(168)]    // max
    public void Validate_RollupLookbackHours_InRange_Passes(int value)
    {
        var opts = new KpiHistoryOptions { RollupLookbackHours = value };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(169)]
    [InlineData(-1)]
    public void Validate_RollupLookbackHours_OutOfRange_Fails(int value)
    {
        var opts = new KpiHistoryOptions { RollupLookbackHours = value };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RollupLookbackHours), StringComparison.Ordinal));
    }

    // ---------------------------------------------------------------------
    // RollupRetentionDays in [1, 3650] AND >= RetentionDays
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(90)]     // == RetentionDays default
    [InlineData(365)]    // default
    [InlineData(3650)]   // max
    public void Validate_RollupRetentionDays_InRange_Passes(int value)
    {
        var opts = new KpiHistoryOptions { RollupRetentionDays = value };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(3651)]
    [InlineData(-1)]
    public void Validate_RollupRetentionDays_OutOfRange_Fails(int value)
    {
        var opts = new KpiHistoryOptions { RollupRetentionDays = value };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RollupRetentionDays), StringComparison.Ordinal));
    }

    [Fact]
    public void Validate_RollupRetentionDays_LessThanRetentionDays_Fails()
    {
        // Coherence rule: rollups must outlive raw samples, else long-range trends hit a hole.
        var opts = new KpiHistoryOptions
        {
            RetentionDays = 90,
            RollupRetentionDays = 30, // in [1,3650] but < RetentionDays
        };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RollupRetentionDays), StringComparison.Ordinal)
                 && f.Contains(nameof(KpiHistoryOptions.RetentionDays), StringComparison.Ordinal));
    }

    [Fact]
    public void Validate_RollupRetentionDays_EqualToRetentionDays_Passes()
    {
        // Boundary of the coherence rule (>=): equal retention is allowed.
        var opts = new KpiHistoryOptions { RetentionDays = 120, RollupRetentionDays = 120 };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    // ---------------------------------------------------------------------
    // RollupThresholdHours >= 24
    // ---------------------------------------------------------------------

    [Theory]
    [InlineData(24)]     // min
    [InlineData(168)]    // default
    [InlineData(2160)]   // 90 d
    public void Validate_RollupThresholdHours_AtOrAboveMin_Passes(int value)
    {
        var opts = new KpiHistoryOptions { RollupThresholdHours = value };
        Assert.True(NewValidator().Validate(null, opts).Succeeded);
    }

    [Theory]
    [InlineData(23)]
    [InlineData(0)]
    [InlineData(-1)]
    public void Validate_RollupThresholdHours_BelowMin_Fails(int value)
    {
        var opts = new KpiHistoryOptions { RollupThresholdHours = value };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Contains(
            result.Failures!,
            f => f.Contains(nameof(KpiHistoryOptions.RollupThresholdHours), StringComparison.Ordinal));
    }

    [Fact]
    public void Validate_AllRulesViolated_AccumulatesEveryFailure()
    {
        // The base class aggregates ALL failures, not just the first — one
        // message per violated rule.
        var opts = new KpiHistoryOptions
        {
            SampleInterval = TimeSpan.Zero,
            RetentionDays = 0,
            PurgeInterval = TimeSpan.Zero,
            DefaultMaxSeriesPoints = 1,
        };
        var result = NewValidator().Validate(null, opts);

        Assert.False(result.Succeeded);
        Assert.Equal(4, result.Failures!.Count());
    }
}
