using ZB.MOM.WW.Auth.ApiKeys;

namespace ZB.MOM.WW.Auth.ApiKeys.Tests;

public class ApiKeySecretHasherTests
{
    private const string Secret = "mysecret";
    private const string Pepper = "mypepper";

    // --- Hash determinism ---

    [Fact]
    public void Hash_SameInputs_ProducesIdenticalHashes()
    {
        byte[] first = ApiKeySecretHasher.Hash(Secret, Pepper);
        byte[] second = ApiKeySecretHasher.Hash(Secret, Pepper);

        Assert.Equal(first, second);
    }

    [Fact]
    public void Hash_DifferentPepper_ProducesDifferentHash()
    {
        byte[] withPepper1 = ApiKeySecretHasher.Hash(Secret, "pepper1");
        byte[] withPepper2 = ApiKeySecretHasher.Hash(Secret, "pepper2");

        Assert.NotEqual(withPepper1, withPepper2);
    }

    [Fact]
    public void Hash_DifferentSecret_ProducesDifferentHash()
    {
        byte[] hash1 = ApiKeySecretHasher.Hash("secret1", Pepper);
        byte[] hash2 = ApiKeySecretHasher.Hash("secret2", Pepper);

        Assert.NotEqual(hash1, hash2);
    }

    [Fact]
    public void Hash_ReturnsThirtyTwoBytes()
    {
        // HMAC-SHA256 output is 256 bits = 32 bytes
        byte[] hash = ApiKeySecretHasher.Hash(Secret, Pepper);

        Assert.Equal(32, hash.Length);
    }

    // --- Verify happy path ---

    [Fact]
    public void Verify_CorrectSecretAndPepper_ReturnsTrue()
    {
        byte[] hash = ApiKeySecretHasher.Hash(Secret, Pepper);

        Assert.True(ApiKeySecretHasher.Verify(Secret, Pepper, hash));
    }

    [Fact]
    public void Verify_WrongSecret_ReturnsFalse()
    {
        byte[] hash = ApiKeySecretHasher.Hash(Secret, Pepper);

        Assert.False(ApiKeySecretHasher.Verify("wrongsecret", Pepper, hash));
    }

    [Fact]
    public void Verify_WrongPepper_ReturnsFalse()
    {
        byte[] hash = ApiKeySecretHasher.Hash(Secret, Pepper);

        Assert.False(ApiKeySecretHasher.Verify(Secret, "wrongpepper", hash));
    }

    // --- Constant-time: length mismatch must not throw ---

    [Fact]
    public void Verify_HashOfDifferentLength_ReturnsFalseWithoutThrowing()
    {
        // A hash of a completely different length — FixedTimeEquals must handle it
        // without throwing and return false.
        byte[] shortHash = [1, 2, 3];

        var exception = Record.Exception(() => ApiKeySecretHasher.Verify(Secret, Pepper, shortHash));

        Assert.Null(exception);
        Assert.False(ApiKeySecretHasher.Verify(Secret, Pepper, shortHash));
    }

    [Fact]
    public void Verify_EmptyHash_ReturnsFalseWithoutThrowing()
    {
        byte[] emptyHash = [];

        var exception = Record.Exception(() => ApiKeySecretHasher.Verify(Secret, Pepper, emptyHash));

        Assert.Null(exception);
        Assert.False(ApiKeySecretHasher.Verify(Secret, Pepper, emptyHash));
    }
}
