using ZB.MOM.WW.ScadaBridge.Commons.Types.Transport;

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

/// <summary>
/// Commons-015: <see cref="EncryptionMetadata"/> must reject malformed envelopes at
/// the type boundary (unknown algorithm, unsupported KDF, sub-minimum or over-cap
/// iteration counts, null salt/IV). Valid construction must round-trip the fields.
/// </summary>
public sealed class EncryptionMetadataTests
{
    [Fact]
    public void Constructor_WithDocumentedValues_Succeeds()
    {
        // 600_000 is the design-doc production value; "abc"/"def" are placeholder
        // Base64 strings, kept short for test legibility.
        var meta = new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: "abc",
            IvB64: "def");

        Assert.Equal("AES-256-GCM", meta.Algorithm);
        Assert.Equal("PBKDF2-SHA256", meta.Kdf);
        Assert.Equal(600_000, meta.Iterations);
        Assert.Equal("abc", meta.SaltB64);
        Assert.Equal("def", meta.IvB64);
    }

    [Theory]
    [InlineData("AES-128-CBC")]      // weaker algorithm
    [InlineData("AES-256-CBC")]      // unauthenticated mode
    [InlineData("aes-256-gcm")]      // case must match exactly
    [InlineData("")]
    [InlineData("FOO")]
    public void Constructor_UnknownAlgorithm_Throws(string algorithm)
    {
        var ex = Assert.Throws<ArgumentException>(() => new EncryptionMetadata(
            Algorithm: algorithm,
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: "abc",
            IvB64: "def"));

        Assert.Equal("Algorithm", ex.ParamName);
        Assert.Contains("AES-256-GCM", ex.Message);
    }

    [Theory]
    [InlineData("PBKDF2-SHA1")]      // weaker hash
    [InlineData("argon2id")]         // unsupported KDF
    [InlineData("pbkdf2-sha256")]    // case must match
    [InlineData("")]
    public void Constructor_UnknownKdf_Throws(string kdf)
    {
        var ex = Assert.Throws<ArgumentException>(() => new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: kdf,
            Iterations: 600_000,
            SaltB64: "abc",
            IvB64: "def"));

        Assert.Equal("Kdf", ex.ParamName);
        Assert.Contains("PBKDF2-SHA256", ex.Message);
    }

    [Theory]
    [InlineData(0)]
    [InlineData(-1)]
    [InlineData(1)]
    [InlineData(99_999)]                  // one below the floor
    [InlineData(10_000_001)]              // one above the ceiling
    [InlineData(int.MaxValue)]
    public void Constructor_IterationsOutOfRange_Throws(int iterations)
    {
        var ex = Assert.Throws<ArgumentException>(() => new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: iterations,
            SaltB64: "abc",
            IvB64: "def"));

        Assert.Equal("Iterations", ex.ParamName);
    }

    [Theory]
    [InlineData(100_000)]                 // OWASP minimum (exact)
    [InlineData(600_000)]                 // design-doc production value
    [InlineData(10_000_000)]              // ceiling (exact)
    public void Constructor_IterationsAtBoundary_Succeeds(int iterations)
    {
        // Exercises the inclusive boundary check on both ends.
        var meta = new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: iterations,
            SaltB64: "abc",
            IvB64: "def");

        Assert.Equal(iterations, meta.Iterations);
    }

    [Fact]
    public void Constructor_NullSalt_Throws()
    {
        // null is rejected; empty is permitted (the seed pattern used by BundleSerializer.Pack).
        Assert.Throws<ArgumentNullException>(() => new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: null!,
            IvB64: "def"));
    }

    [Fact]
    public void Constructor_NullIv_Throws()
    {
        Assert.Throws<ArgumentNullException>(() => new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: "abc",
            IvB64: null!));
    }

    [Fact]
    public void Constructor_EmptySaltAndIv_Succeeds_ForSeedPattern()
    {
        // BundleSerializer.Pack re-stamps salt/iv from the ciphertext it actually
        // writes, so callers (BundleExporter) construct a seed instance with empty
        // placeholders. Validation must therefore accept empty here.
        var seed = new EncryptionMetadata(
            Algorithm: "AES-256-GCM",
            Kdf: "PBKDF2-SHA256",
            Iterations: 600_000,
            SaltB64: string.Empty,
            IvB64: string.Empty);

        Assert.Equal(string.Empty, seed.SaltB64);
        Assert.Equal(string.Empty, seed.IvB64);
    }
}
