using Microsoft.Extensions.Options;

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

/// <summary>
/// Arch-review 08 §1.5: <see cref="TransportOptions"/> feeds bundle size caps,
/// zip-bomb defences, unlock rate limits and — security-critically — the PBKDF2
/// iteration count used to derive the bundle encryption key. A zero/negative or
/// otherwise degenerate value must be rejected at startup with a clear,
/// key-naming message rather than silently weakening key derivation or crashing
/// deep inside the import pipeline.
/// </summary>
public class TransportOptionsValidatorTests
{
    private static ValidateOptionsResult Validate(TransportOptions options) =>
        new TransportOptionsValidator().Validate(Options.DefaultName, options);

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

    [Fact]
    public void ZeroBundleSessionTtlMinutes_IsRejected()
    {
        var result = Validate(new TransportOptions { BundleSessionTtlMinutes = 0 });
        Assert.True(result.Failed);
        Assert.Contains("BundleSessionTtlMinutes", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxBundleSizeMb_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxBundleSizeMb = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxBundleSizeMb", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxConcurrentImportSessions_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxConcurrentImportSessions = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxConcurrentImportSessions", result.FailureMessage);   // FAILS today: validates clean
    }

    [Fact]
    public void NegativeMaxConcurrentImportSessions_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxConcurrentImportSessions = -1 });
        Assert.True(result.Failed);
        Assert.Contains("MaxConcurrentImportSessions", result.FailureMessage);
    }

    [Fact]
    public void ZeroPbkdf2Iterations_IsRejected()
    {
        var result = Validate(new TransportOptions { Pbkdf2Iterations = 0 });
        Assert.True(result.Failed);
        Assert.Contains("Pbkdf2Iterations", result.FailureMessage);
    }

    [Fact]
    public void Pbkdf2Iterations_BelowSecurityFloor_IsRejected()
    {
        // A config typo (e.g. 6_000 instead of 600_000) must not silently weaken
        // bundle key derivation: anything under 100_000 is rejected.
        var result = Validate(new TransportOptions { Pbkdf2Iterations = 99_999 });
        Assert.True(result.Failed);
        Assert.Contains("Pbkdf2Iterations", result.FailureMessage);
    }

    [Fact]
    public void Pbkdf2Iterations_AtSecurityFloor_IsValid()
    {
        var result = Validate(new TransportOptions { Pbkdf2Iterations = 100_000 });
        Assert.True(result.Succeeded, result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxUnlockAttemptsPerSession_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxUnlockAttemptsPerSession = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxUnlockAttemptsPerSession", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxBundleEntryDecompressedMb_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxBundleEntryDecompressedMb = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxBundleEntryDecompressedMb", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxBundleEntryCount_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxBundleEntryCount = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxBundleEntryCount", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxBundleEntryCompressionRatio_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxBundleEntryCompressionRatio = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxBundleEntryCompressionRatio", result.FailureMessage);
    }

    [Fact]
    public void ZeroMaxUnlockAttemptsPerIpPerHour_IsRejected()
    {
        var result = Validate(new TransportOptions { MaxUnlockAttemptsPerIpPerHour = 0 });
        Assert.True(result.Failed);
        Assert.Contains("MaxUnlockAttemptsPerIpPerHour", result.FailureMessage);
    }

    [Fact]
    public void ZeroSchemaVersionMajor_IsRejected()
    {
        var result = Validate(new TransportOptions { SchemaVersionMajor = 0 });
        Assert.True(result.Failed);
        Assert.Contains("SchemaVersionMajor", result.FailureMessage);
    }

    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    public void BlankSourceEnvironment_IsRejected(string value)
    {
        var result = Validate(new TransportOptions { SourceEnvironment = value });
        Assert.True(result.Failed);
        Assert.Contains("SourceEnvironment", result.FailureMessage);
    }
}
