using ZB.MOM.WW.ScadaBridge.Host;

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

/// <summary>
/// Eager startup validation for the shared Host options (arch-review 08 round 2
/// NF4). Covers <see cref="NodeOptionsValidator"/>,
/// <see cref="DatabaseOptionsValidator"/>, and
/// <see cref="LoggingOptionsValidator"/> in one file. The headline case: an
/// empty <see cref="NodeOptions.NodeName"/> normalises to a NULL SourceNode
/// audit column, so it must fail the host at boot rather than silently degrade
/// the audit trail.
/// </summary>
public class NodeOptionsValidatorTests
{
    // ---- NodeOptions ----

    [Fact]
    public void Node_DefaultsWithNodeName_AreValid()
    {
        var validator = new NodeOptionsValidator();
        Assert.True(validator.Validate(null, new NodeOptions { NodeName = "node-a" }).Succeeded);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    public void Node_EmptyOrWhitespaceNodeName_Fails(string nodeName)
    {
        var validator = new NodeOptionsValidator();
        var result = validator.Validate(null, new NodeOptions { NodeName = nodeName });
        Assert.False(result.Succeeded);
        Assert.Contains(result.Failures!,
            f => f.Contains(nameof(NodeOptions.NodeName), StringComparison.Ordinal));
    }

    [Fact]
    public void Node_ZeroRemotingPort_IsValid()
    {
        // CompositionRootTests uses RemotingPort = 0 to request a dynamic port.
        var validator = new NodeOptionsValidator();
        Assert.True(validator.Validate(null,
            new NodeOptions { NodeName = "node-a", RemotingPort = 0 }).Succeeded);
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(65_536)]
    public void Node_PortOutOfRange_Fails(int port)
    {
        var validator = new NodeOptionsValidator();
        var result = validator.Validate(null,
            new NodeOptions { NodeName = "node-a", GrpcPort = port });
        Assert.False(result.Succeeded);
        Assert.Contains(result.Failures!,
            f => f.Contains(nameof(NodeOptions.GrpcPort), StringComparison.Ordinal));
    }

    // ---- DatabaseOptions ----

    [Fact]
    public void Database_AllNull_IsValid()
    {
        // All three fields are nullable and role-dependent; null stays valid.
        var validator = new DatabaseOptionsValidator();
        Assert.True(validator.Validate(null, new DatabaseOptions()).Succeeded);
    }

    [Fact]
    public void Database_WhitespaceValue_Fails()
    {
        var validator = new DatabaseOptionsValidator();
        var result = validator.Validate(null, new DatabaseOptions { ConfigurationDb = "   " });
        Assert.False(result.Succeeded);
        Assert.Contains(result.Failures!,
            f => f.Contains(nameof(DatabaseOptions.ConfigurationDb), StringComparison.Ordinal));
    }

    // ---- LoggingOptions ----

    [Fact]
    public void Logging_DefaultInformation_IsValid()
    {
        var validator = new LoggingOptionsValidator();
        Assert.True(validator.Validate(null, new LoggingOptions()).Succeeded);
    }

    [Theory]
    [InlineData("Verbose")]
    [InlineData("debug")]
    [InlineData("FATAL")]
    public void Logging_RecognisedLevel_IsValid(string level)
    {
        var validator = new LoggingOptionsValidator();
        Assert.True(validator.Validate(null, new LoggingOptions { MinimumLevel = level }).Succeeded);
    }

    [Fact]
    public void Logging_UnknownLevel_Fails()
    {
        var validator = new LoggingOptionsValidator();
        var result = validator.Validate(null, new LoggingOptions { MinimumLevel = "Chatty" });
        Assert.False(result.Succeeded);
        Assert.Contains(result.Failures!,
            f => f.Contains(nameof(LoggingOptions.MinimumLevel), StringComparison.Ordinal));
    }
}
