using NATS.Server.Protocol;

namespace NATS.Server.Core.Tests.ProtocolParity;

public class ProtoWireParityTests
{
    [Fact]
    public void ScanField_reads_tag_and_value_size_for_length_delimited_field()
    {
        // field=2, type=2, len=3, bytes=abc
        byte[] bytes = [0x12, 0x03, (byte)'a', (byte)'b', (byte)'c'];

        var (number, wireType, size) = ProtoWire.ScanField(bytes);

        number.ShouldBe(2);
        wireType.ShouldBe(2);
        size.ShouldBe(5);
    }

    [Fact]
    public void ScanTag_rejects_invalid_field_numbers()
    {
        var zeroFieldEx = Should.Throw<ProtoWireException>(() => ProtoWire.ScanTag([0x00]));
        zeroFieldEx.Message.ShouldBe(ProtoWire.ErrProtoInvalidFieldNumber);

        var tooLargeTag = ProtoWire.EncodeVarint(((ulong)int.MaxValue + 1UL) << 3);
        var tooLargeEx = Should.Throw<ProtoWireException>(() => ProtoWire.ScanTag(tooLargeTag));
        tooLargeEx.Message.ShouldBe(ProtoWire.ErrProtoInvalidFieldNumber);
    }

    [Fact]
    public void ScanFieldValue_supports_expected_wire_types()
    {
        ProtoWire.ScanFieldValue(5, [0, 0, 0, 0]).ShouldBe(4);
        ProtoWire.ScanFieldValue(1, [0, 0, 0, 0, 0, 0, 0, 0]).ShouldBe(8);
        ProtoWire.ScanFieldValue(0, [0x01]).ShouldBe(1);
    }

    [Fact]
    public void ScanFieldValue_rejects_unsupported_wire_type()
    {
        var ex = Should.Throw<ProtoWireException>(() => ProtoWire.ScanFieldValue(3, [0x00]));
        ex.Message.ShouldBe("unsupported type: 3");
    }

    [Fact]
    public void ScanVarint_reports_insufficient_and_overflow_errors()
    {
        var insufficient = Should.Throw<ProtoWireException>(() => ProtoWire.ScanVarint([0x80]));
        insufficient.Message.ShouldBe(ProtoWire.ErrProtoInsufficient);

        byte[] overflow = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x02];
        var tooLarge = Should.Throw<ProtoWireException>(() => ProtoWire.ScanVarint(overflow));
        tooLarge.Message.ShouldBe(ProtoWire.ErrProtoOverflow);
    }

    [Fact]
    public void ScanBytes_reports_insufficient_when_length_prefix_exceeds_payload()
    {
        var ex = Should.Throw<ProtoWireException>(() => ProtoWire.ScanBytes([0x04, 0x01, 0x02]));
        ex.Message.ShouldBe(ProtoWire.ErrProtoInsufficient);
    }

    [Fact]
    public void EncodeVarint_round_trips_values_via_scan_varint()
    {
        ulong[] values =
        [
            0UL,
            1UL,
            127UL,
            128UL,
            16_383UL,
            16_384UL,
            (1UL << 32) - 1,
            ulong.MaxValue,
        ];

        foreach (var value in values)
        {
            var encoded = ProtoWire.EncodeVarint(value);
            var (decoded, size) = ProtoWire.ScanVarint(encoded);
            decoded.ShouldBe(value);
            size.ShouldBe(encoded.Length);
        }
    }
}
