using NSubstitute;
using ScadaLink.Commons.Entities.InboundApi;
using ScadaLink.Commons.Interfaces.Repositories;
using ScadaLink.Commons.Types.InboundApi;

namespace ScadaLink.InboundAPI.Tests;

/// <summary>
/// ConfigurationDatabase-012: <see cref="ApiKeyValidator"/> must authenticate by
/// hashing the presented candidate with the same HMAC-SHA256 pepper used at
/// creation, then comparing against the stored <see cref="ApiKey.KeyHash"/> — never
/// against a plaintext key. The comparison stays constant-time.
/// </summary>
public class ApiKeyHashValidationTests
{
    private const string Pepper = "a-sufficiently-long-server-side-pepper-value";

    private readonly IInboundApiRepository _repository = Substitute.For<IInboundApiRepository>();

    private static ApiKey StoredKey(ApiKeyHasher hasher, string liveKey, int id = 1, bool enabled = true)
    {
        var key = ApiKey.FromHash("MES-Production", hasher.Hash(liveKey));
        key.Id = id;
        key.IsEnabled = enabled;
        return key;
    }

    [Fact]
    public async Task ValidateAsync_WithPepperedHasher_AcceptsKeyHashedWithSamePepper()
    {
        var hasher = new ApiKeyHasher(Pepper);
        var stored = StoredKey(hasher, "live-secret-key");
        var method = new ApiMethod("ingest", "return 1;") { Id = 10 };

        _repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { stored });
        _repository.GetMethodByNameAsync("ingest").Returns(method);
        _repository.GetApprovedKeysForMethodAsync(10).Returns(new List<ApiKey> { stored });

        var validator = new ApiKeyValidator(_repository, hasher);

        var result = await validator.ValidateAsync("live-secret-key", "ingest");

        Assert.True(result.IsValid);
        Assert.Equal(200, result.StatusCode);
    }

    [Fact]
    public async Task ValidateAsync_WrongKey_FailsEvenWhenItHashesToSomethingNonNull()
    {
        var hasher = new ApiKeyHasher(Pepper);
        var stored = StoredKey(hasher, "the-real-key");

        _repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { stored });

        var validator = new ApiKeyValidator(_repository, hasher);

        var result = await validator.ValidateAsync("a-wrong-key", "ingest");

        Assert.False(result.IsValid);
        Assert.Equal(401, result.StatusCode);
    }

    [Fact]
    public async Task ValidateAsync_StoredHashIsNotThePlaintextKey()
    {
        // Sanity guard: the value the validator compares against must be a hash, not
        // the live secret — a DB dump must not yield a usable credential.
        var hasher = new ApiKeyHasher(Pepper);
        var stored = StoredKey(hasher, "live-secret-key");

        Assert.NotEqual("live-secret-key", stored.KeyHash);

        _repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { stored });
        var validator = new ApiKeyValidator(_repository, hasher);

        // Presenting the stored hash itself must NOT authenticate — only the live key does.
        var result = await validator.ValidateAsync(stored.KeyHash, "ingest");
        Assert.False(result.IsValid);
    }

    [Fact]
    public async Task ValidateAsync_KeyHashedUnderADifferentPepper_DoesNotAuthenticate()
    {
        var creationHasher = new ApiKeyHasher(Pepper);
        var stored = StoredKey(creationHasher, "live-secret-key");

        _repository.GetAllApiKeysAsync().Returns(new List<ApiKey> { stored });

        // A validator running with a different pepper cannot recognise the key.
        var otherHasher = new ApiKeyHasher("a-totally-different-server-side-pepper-val");
        var validator = new ApiKeyValidator(_repository, otherHasher);

        var result = await validator.ValidateAsync("live-secret-key", "ingest");
        Assert.False(result.IsValid);
    }
}
