using Microsoft.Extensions.Options;

namespace ScadaLink.ClusterInfrastructure.Tests;

/// <summary>
/// CI-004: Tests that <see cref="ClusterOptionsValidator"/> rejects the
/// catastrophic misconfigurations the design doc warns against — a
/// <c>MinNrOfMembers</c> other than 1, an unsupported split-brain strategy,
/// empty seed nodes, and timings where the heartbeat is not below the
/// failure-detection threshold.
/// </summary>
public class ClusterOptionsValidatorTests
{
    private static ClusterOptions ValidOptions() => new()
    {
        SeedNodes = new List<string> { "akka.tcp://scadalink@node1:8081", "akka.tcp://scadalink@node2:8081" },
        SplitBrainResolverStrategy = "keep-oldest",
        StableAfter = TimeSpan.FromSeconds(15),
        HeartbeatInterval = TimeSpan.FromSeconds(2),
        FailureDetectionThreshold = TimeSpan.FromSeconds(10),
        MinNrOfMembers = 1,
        DownIfAlone = true
    };

    [Fact]
    public void DefaultOptions_AreValid()
    {
        var result = new ClusterOptionsValidator().Validate(null, ValidOptions());

        Assert.True(result.Succeeded, result.FailureMessage);
    }

    [Fact]
    public void MinNrOfMembers_NotOne_FailsValidation()
    {
        var options = ValidOptions();
        options.MinNrOfMembers = 2;

        var result = new ClusterOptionsValidator().Validate(null, options);

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

    [Theory]
    [InlineData("keep-majority")]
    [InlineData("static-quorum")]
    [InlineData("nonsense")]
    public void UnsupportedSplitBrainStrategy_FailsValidation(string strategy)
    {
        var options = ValidOptions();
        options.SplitBrainResolverStrategy = strategy;

        var result = new ClusterOptionsValidator().Validate(null, options);

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

    [Fact]
    public void EmptySeedNodes_FailsValidation()
    {
        var options = ValidOptions();
        options.SeedNodes = new List<string>();

        var result = new ClusterOptionsValidator().Validate(null, options);

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

    [Fact]
    public void HeartbeatNotBelowFailureThreshold_FailsValidation()
    {
        var options = ValidOptions();
        options.HeartbeatInterval = TimeSpan.FromSeconds(10);
        options.FailureDetectionThreshold = TimeSpan.FromSeconds(10);

        var result = new ClusterOptionsValidator().Validate(null, options);

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

    [Fact]
    public void NonPositiveStableAfter_FailsValidation()
    {
        var options = ValidOptions();
        options.StableAfter = TimeSpan.Zero;

        var result = new ClusterOptionsValidator().Validate(null, options);

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

    [Fact]
    public void Validate_AccumulatesAllFailures()
    {
        var options = new ClusterOptions
        {
            SeedNodes = new List<string>(),
            SplitBrainResolverStrategy = "keep-majority",
            MinNrOfMembers = 2,
            StableAfter = TimeSpan.Zero,
            HeartbeatInterval = TimeSpan.FromSeconds(20),
            FailureDetectionThreshold = TimeSpan.FromSeconds(10)
        };

        var result = new ClusterOptionsValidator().Validate(null, options);

        Assert.True(result.Failed);
        Assert.Contains("SeedNodes", result.FailureMessage);
        Assert.Contains("SplitBrainResolverStrategy", result.FailureMessage);
        Assert.Contains("MinNrOfMembers", result.FailureMessage);
        Assert.Contains("StableAfter", result.FailureMessage);
        Assert.Contains("HeartbeatInterval", result.FailureMessage);
    }
}
