// Copyright 2020-2026 The NATS Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0

using System.Buffers.Binary;

namespace ZB.MOM.NatsNet.Server.Mqtt;

internal sealed class MqttReader
{
    private byte[] _buffer = [];
    private byte[] _pendingBuffer = [];

    public int Position { get; private set; }

    public int PacketStart { get; set; }

    public void Reset(byte[] buffer)
    {
        if (_pendingBuffer.Length > 0)
        {
            var merged = new byte[_pendingBuffer.Length + buffer.Length];
            Buffer.BlockCopy(_pendingBuffer, 0, merged, 0, _pendingBuffer.Length);
            Buffer.BlockCopy(buffer, 0, merged, _pendingBuffer.Length, buffer.Length);
            buffer = merged;
            _pendingBuffer = [];
        }

        _buffer = buffer;
        Position = 0;
        PacketStart = 0;
    }

    public bool HasMore()
    {
        return Position < _buffer.Length;
    }

    public byte ReadByte(string field)
    {
        if (Position >= _buffer.Length)
        {
            throw new InvalidOperationException($"error reading {field}: {nameof(EndOfStreamException)}");
        }

        return _buffer[Position++];
    }

    public (int Length, bool Complete) ReadPacketLen()
    {
        return ReadPacketLenWithCheck(check: true);
    }

    public (int Length, bool Complete) ReadPacketLenWithCheck(bool check)
    {
        var multiplier = 1;
        var value = 0;

        while (Position < _buffer.Length)
        {
            var b = _buffer[Position++];
            value += (b & 0x7F) * multiplier;

            if ((b & 0x80) == 0)
            {
                if (check && Position + value > _buffer.Length)
                {
                    break;
                }

                return (value, true);
            }

            multiplier *= 0x80;
            if (multiplier > 0x200000)
            {
                throw new InvalidOperationException("malformed MQTT variable integer");
            }
        }

        _pendingBuffer = _buffer[PacketStart..];
        return (0, false);
    }

    public string ReadString(string field)
    {
        var data = ReadBytes(field, copy: false);
        return data.Length == 0 ? string.Empty : System.Text.Encoding.UTF8.GetString(data);
    }

    public byte[] ReadBytes(string field, bool copy)
    {
        var length = ReadUInt16(field);
        if (length == 0)
        {
            return [];
        }

        if (Position + length > _buffer.Length)
        {
            throw new InvalidOperationException($"error reading {field}: {nameof(EndOfStreamException)}");
        }

        var start = Position;
        Position += length;

        if (!copy)
        {
            return _buffer[start..Position];
        }

        var result = new byte[length];
        Buffer.BlockCopy(_buffer, start, result, 0, length);
        return result;
    }

    public ushort ReadUInt16(string field)
    {
        if (_buffer.Length - Position < 2)
        {
            throw new InvalidOperationException($"error reading {field}: {nameof(EndOfStreamException)}");
        }

        var value = BinaryPrimitives.ReadUInt16BigEndian(_buffer.AsSpan(Position, 2));
        Position += 2;
        return value;
    }

    /// <summary>
    /// Reads exactly <paramref name="count"/> raw bytes without a length prefix.
    /// Used for PUBLISH payloads where the length is known from the remaining length field.
    /// </summary>
    public byte[] ReadSlice(int count, string field)
    {
        if (count == 0)
            return [];

        if (Position + count > _buffer.Length)
            throw new InvalidOperationException($"error reading {field}: {nameof(EndOfStreamException)}");

        var start = Position;
        Position += count;
        return _buffer[start..Position];
    }
}
