using NATS.Server.Gateways;

namespace NATS.Server.Tests.Gateways;

/// <summary>
/// Tests for the expanded ReplyMapper with hash support.
/// Covers new format (_GR_.{clusterId}.{hash}.{reply}), legacy format (_GR_.{clusterId}.{reply}),
/// cluster/hash extraction, and FNV-1a hash determinism.
/// Go reference: gateway.go:2000-2100, gateway.go:340-380.
/// </summary>
public class ReplyMapperFullTests
{
    // Go: gateway.go — replyPfx includes cluster hash + server hash segments
    [Fact]
    public void ToGatewayReply_WithHash_IncludesHashSegment()
    {
        var result = ReplyMapper.ToGatewayReply("_INBOX.abc123", "clusterA", 42);

        result.ShouldNotBeNull();
        result.ShouldBe("_GR_.clusterA.42._INBOX.abc123");
    }

    // Go: gateway.go — hash is deterministic based on reply subject
    [Fact]
    public void ToGatewayReply_AutoHash_IsDeterministic()
    {
        var result1 = ReplyMapper.ToGatewayReply("_INBOX.xyz", "cluster1");
        var result2 = ReplyMapper.ToGatewayReply("_INBOX.xyz", "cluster1");

        result1.ShouldNotBeNull();
        result2.ShouldNotBeNull();
        result1.ShouldBe(result2);

        // Should contain the hash segment between cluster and reply
        result1!.ShouldStartWith("_GR_.cluster1.");
        result1.ShouldEndWith("._INBOX.xyz");

        // Parse the hash segment
        var afterPrefix = result1["_GR_.cluster1.".Length..];
        var dotIdx = afterPrefix.IndexOf('.');
        dotIdx.ShouldBeGreaterThan(0);
        var hashStr = afterPrefix[..dotIdx];
        long.TryParse(hashStr, out var hash).ShouldBeTrue();
        hash.ShouldBeGreaterThan(0);
    }

    // Go: handleGatewayReply — strips _GR_ prefix + cluster + hash to restore original
    [Fact]
    public void TryRestoreGatewayReply_WithHash_RestoresOriginal()
    {
        var hash = ReplyMapper.ComputeReplyHash("reply.subject");
        var mapped = ReplyMapper.ToGatewayReply("reply.subject", "clusterB", hash);

        var success = ReplyMapper.TryRestoreGatewayReply(mapped, out var restored);

        success.ShouldBeTrue();
        restored.ShouldBe("reply.subject");
    }

    // Go: handleGatewayReply — legacy $GR. and old _GR_ formats without hash
    [Fact]
    public void TryRestoreGatewayReply_LegacyNoHash_StillWorks()
    {
        // Legacy format: _GR_.{clusterId}.{reply} (no hash segment)
        // The reply itself starts with a non-numeric character, so it won't be mistaken for a hash.
        var legacyReply = "_GR_.clusterX.my.reply.subject";

        var success = ReplyMapper.TryRestoreGatewayReply(legacyReply, out var restored);

        success.ShouldBeTrue();
        restored.ShouldBe("my.reply.subject");
    }

    // Go: handleGatewayReply — nested _GR_ prefixes from multi-hop gateways
    [Fact]
    public void TryRestoreGatewayReply_NestedPrefixes_UnwrapsAll()
    {
        // Inner: _GR_.cluster1.{hash}.original.reply
        var hash1 = ReplyMapper.ComputeReplyHash("original.reply");
        var inner = ReplyMapper.ToGatewayReply("original.reply", "cluster1", hash1);

        // Outer: _GR_.cluster2.{hash2}.{inner}
        var hash2 = ReplyMapper.ComputeReplyHash(inner!);
        var outer = ReplyMapper.ToGatewayReply(inner, "cluster2", hash2);

        var success = ReplyMapper.TryRestoreGatewayReply(outer, out var restored);

        success.ShouldBeTrue();
        restored.ShouldBe("original.reply");
    }

    // Go: gateway.go — cluster hash extraction for routing decisions
    [Fact]
    public void TryExtractClusterId_ValidReply_ExtractsId()
    {
        var mapped = ReplyMapper.ToGatewayReply("test.reply", "myCluster", 999);

        var success = ReplyMapper.TryExtractClusterId(mapped, out var clusterId);

        success.ShouldBeTrue();
        clusterId.ShouldBe("myCluster");
    }

    // Go: gateway.go — hash extraction for reply deduplication
    [Fact]
    public void TryExtractHash_ValidReply_ExtractsHash()
    {
        var mapped = ReplyMapper.ToGatewayReply("inbox.abc", "clusterZ", 12345);

        var success = ReplyMapper.TryExtractHash(mapped, out var hash);

        success.ShouldBeTrue();
        hash.ShouldBe(12345);
    }

    // Go: getGWHash — hash must be deterministic for same input
    [Fact]
    public void ComputeReplyHash_Deterministic()
    {
        var hash1 = ReplyMapper.ComputeReplyHash("_INBOX.test123");
        var hash2 = ReplyMapper.ComputeReplyHash("_INBOX.test123");

        hash1.ShouldBe(hash2);
        hash1.ShouldBeGreaterThan(0);
    }

    // Go: getGWHash — different inputs should produce different hashes
    [Fact]
    public void ComputeReplyHash_DifferentInputs_DifferentHashes()
    {
        var hash1 = ReplyMapper.ComputeReplyHash("_INBOX.aaa");
        var hash2 = ReplyMapper.ComputeReplyHash("_INBOX.bbb");
        var hash3 = ReplyMapper.ComputeReplyHash("reply.subject.1");

        hash1.ShouldNotBe(hash2);
        hash1.ShouldNotBe(hash3);
        hash2.ShouldNotBe(hash3);
    }

    // Go: isGWRoutedReply — plain subjects should not match gateway prefix
    [Fact]
    public void HasGatewayReplyPrefix_PlainSubject_ReturnsFalse()
    {
        ReplyMapper.HasGatewayReplyPrefix("foo.bar").ShouldBeFalse();
        ReplyMapper.HasGatewayReplyPrefix("_INBOX.test").ShouldBeFalse();
        ReplyMapper.HasGatewayReplyPrefix(null).ShouldBeFalse();
        ReplyMapper.HasGatewayReplyPrefix("").ShouldBeFalse();
        ReplyMapper.HasGatewayReplyPrefix("_GR_").ShouldBeFalse(); // No trailing dot
        ReplyMapper.HasGatewayReplyPrefix("_GR_.cluster.reply").ShouldBeTrue();
    }
}
