// Copyright 2012-2025 The NATS Authors
// Licensed under the Apache License, Version 2.0

using Shouldly;
using ZB.MOM.NatsNet.Server.Internal;

namespace ZB.MOM.NatsNet.Server.Tests.Internal;

public class ProtoWireTests
{
    [Fact]
    public void ProtoScanTag_ValidTag_ReturnsFieldTypeAndSize()
    {
        var (num, typ, size, err) = ProtoWire.ProtoScanTag([0x7A]); // field=15, type=2

        err.ShouldBeNull();
        num.ShouldBe(15);
        typ.ShouldBe(2);
        size.ShouldBe(1);
    }

    [Fact]
    public void ProtoScanTag_InvalidFieldNumber_ReturnsError()
    {
        var (_, _, _, err) = ProtoWire.ProtoScanTag([0x02]); // field=0, type=2

        err.ShouldNotBeNull();
        err.Message.ShouldContain("invalid field number");
    }

    [Fact]
    public void ProtoScanFieldValue_UnsupportedWireType_ReturnsError()
    {
        var (_, err) = ProtoWire.ProtoScanFieldValue(3, [0x01]);

        err.ShouldNotBeNull();
        err.Message.ShouldContain("unsupported type");
    }

    [Fact]
    public void ProtoScanVarint_InsufficientData_ReturnsError()
    {
        var (_, _, err) = ProtoWire.ProtoScanVarint([0x80]);

        err.ShouldNotBeNull();
        err.Message.ShouldContain("insufficient data");
    }

    [Fact]
    public void ProtoScanVarint_Overflow_ReturnsError()
    {
        var (_, _, err) = ProtoWire.ProtoScanVarint([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02]);

        err.ShouldNotBeNull();
        err.Message.ShouldContain("too much data");
    }

    [Fact]
    public void ProtoScanBytes_LengthDelimited_ReturnsLengthPrefixPlusPayloadSize()
    {
        var (size, err) = ProtoWire.ProtoScanBytes([0x03, (byte)'a', (byte)'b', (byte)'c']);

        err.ShouldBeNull();
        size.ShouldBe(4);
    }

    [Fact]
    public void ProtoScanField_ValidLengthDelimited_ReturnsTotalFieldSize()
    {
        var (num, typ, size, err) = ProtoWire.ProtoScanField([0x0A, 0x03, (byte)'a', (byte)'b', (byte)'c']); // field=1,type=2

        err.ShouldBeNull();
        num.ShouldBe(1);
        typ.ShouldBe(2);
        size.ShouldBe(5);
    }

    [Theory]
    [InlineData(1UL << 7, 2)]
    [InlineData(1UL << 14, 3)]
    [InlineData(1UL << 21, 4)]
    [InlineData(1UL << 28, 5)]
    [InlineData(1UL << 35, 6)]
    [InlineData(1UL << 42, 7)]
    [InlineData(1UL << 49, 8)]
    [InlineData(1UL << 56, 9)]
    [InlineData(1UL << 63, 10)]
    public void ProtoEncodeVarint_BoundaryValues_ReturnsExpectedLength(ulong value, int expectedLength)
    {
        var encoded = ProtoWire.ProtoEncodeVarint(value);

        encoded.Length.ShouldBe(expectedLength);
    }

    [Theory]
    [InlineData(1UL << 7)]
    [InlineData(1UL << 14)]
    [InlineData(1UL << 21)]
    [InlineData(1UL << 28)]
    [InlineData(1UL << 35)]
    [InlineData(1UL << 42)]
    [InlineData(1UL << 49)]
    [InlineData(1UL << 56)]
    [InlineData(1UL << 63)]
    public void ProtoEncodeVarint_BoundaryValues_RoundTripThroughProtoScanVarint(ulong value)
    {
        var encoded = ProtoWire.ProtoEncodeVarint(value);
        var (decoded, size, err) = ProtoWire.ProtoScanVarint(encoded);

        err.ShouldBeNull();
        size.ShouldBe(encoded.Length);
        decoded.ShouldBe(value);
    }
}
