using Microsoft.Extensions.Options;

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

/// <summary>
/// <see cref="StoreAndForwardOptions"/> retry intervals feed the background sweep
/// timer (zero/negative trips the timer constructor) and the SQLite path backs the
/// buffer. These tests assert the <see cref="StoreAndForwardOptionsValidator"/>
/// rejects a bad <c>ScadaBridge:StoreAndForward</c> section with a clear,
/// key-naming message rather than crashing later with an opaque exception.
/// </summary>
public class StoreAndForwardOptionsValidatorTests
{
    private static ValidateOptionsResult Validate(StoreAndForwardOptions options) =>
        new StoreAndForwardOptionsValidator().Validate(Options.DefaultName, options);

    [Fact]
    public void DefaultOptions_AreValid()
    {
        var result = Validate(new StoreAndForwardOptions());
        Assert.True(result.Succeeded, result.FailureMessage);
    }

    [Fact]
    public void ZeroDefaultRetryInterval_IsRejected()
    {
        var result = Validate(new StoreAndForwardOptions { DefaultRetryInterval = TimeSpan.Zero });

        Assert.True(result.Failed);
        Assert.Contains("DefaultRetryInterval", result.FailureMessage);
    }

    [Fact]
    public void ZeroRetryTimerInterval_IsRejected()
    {
        var result = Validate(new StoreAndForwardOptions { RetryTimerInterval = TimeSpan.Zero });

        Assert.True(result.Failed);
        Assert.Contains("RetryTimerInterval", result.FailureMessage);
    }

    /// <summary>
    /// The inverse of the rule this replaces. Before LocalDb Phase 2 an empty
    /// SqliteDbPath was rejected, because it was the live buffer file and an empty
    /// value failed opaquely at first enqueue. The buffer now lives in the
    /// consolidated LocalDb database, so the key is only the legacy migration
    /// source and an empty value legitimately means "nothing to migrate" — a node
    /// that has already migrated must be able to drop it.
    /// </summary>
    [Fact]
    public void EmptySqliteDbPath_IsAccepted_BecauseThePathIsMigrationOnly()
    {
        var result = Validate(new StoreAndForwardOptions { SqliteDbPath = "" });

        Assert.False(result.Failed);
    }

    // ── R2 T9: sweep-tuning eager validation (N4) ──

    [Theory]
    [InlineData(-1)]
    [InlineData(-500)]
    public void Validate_NegativeSweepBatchLimit_Fails(int limit)
    {
        var result = Validate(new StoreAndForwardOptions { SweepBatchLimit = limit });
        Assert.True(result.Failed);
        Assert.Contains("SweepBatchLimit", result.FailureMessage);
    }

    [Fact]
    public void Validate_ZeroSweepBatchLimit_IsValidUnlimitedLegacy()
    {
        var result = Validate(new StoreAndForwardOptions { SweepBatchLimit = 0 });
        Assert.True(result.Succeeded, result.FailureMessage);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(-4)]
    public void Validate_NonPositiveSweepTargetParallelism_Fails(int parallelism)
    {
        var result = Validate(new StoreAndForwardOptions { SweepTargetParallelism = parallelism });
        Assert.True(result.Failed);
        Assert.Contains("SweepTargetParallelism", result.FailureMessage);
    }

    // ── WP2.6c: bounded observer queue capacity ──

    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    public void Validate_NonPositiveObserverQueueCapacity_Fails(int capacity)
    {
        var result = Validate(new StoreAndForwardOptions { ObserverQueueCapacity = capacity });
        Assert.True(result.Failed);
        Assert.Contains("ObserverQueueCapacity", result.FailureMessage);
    }
}
