// 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

namespace ZB.MOM.NatsNet.Server.Mqtt;

internal static class MqttSubjectConverter
{
    private const byte Dot = (byte)'.';
    private const byte Slash = (byte)'/';
    private const byte SingleWildcard = (byte)'+';
    private const byte MultiWildcard = (byte)'#';
    private const byte NatsSingleWildcard = (byte)'*';
    private const byte NatsFullWildcard = (byte)'>';

    public static byte[] MqttTopicToNatsPubSubject(ReadOnlySpan<byte> mqttTopic)
    {
        return MqttToNatsSubjectConversion(mqttTopic, wildcardAllowed: false);
    }

    public static byte[] MqttFilterToNatsSubject(ReadOnlySpan<byte> mqttFilter)
    {
        return MqttToNatsSubjectConversion(mqttFilter, wildcardAllowed: true);
    }

    public static byte[] MqttToNatsSubjectConversion(ReadOnlySpan<byte> input, bool wildcardAllowed)
    {
        var output = new List<byte>(input.Length + 8);
        for (var i = 0; i < input.Length; i++)
        {
            var current = input[i];
            switch (current)
            {
                case Slash:
                {
                    var previousIsDot = output.Count > 0 && output[^1] == Dot;
                    if (i == 0 || previousIsDot)
                    {
                        output.Add(Slash);
                        output.Add(Dot);
                    }
                    else if (i == input.Length - 1 || input[i + 1] == Slash)
                    {
                        output.Add(Dot);
                        output.Add(Slash);
                    }
                    else
                    {
                        output.Add(Dot);
                    }

                    break;
                }
                case (byte)' ':
                    throw new ArgumentException("unsupported MQTT topic/filter character: space", nameof(input));
                case Dot:
                    output.Add(Slash);
                    output.Add(Slash);
                    break;
                case SingleWildcard:
                case MultiWildcard:
                    if (!wildcardAllowed)
                    {
                        throw new ArgumentException("wildcards are not allowed in MQTT publish topic", nameof(input));
                    }

                    output.Add(current == SingleWildcard ? NatsSingleWildcard : NatsFullWildcard);
                    break;
                default:
                    output.Add(current);
                    break;
            }
        }

        if (output.Count > 0 && output[^1] == Dot)
        {
            output.Add(Slash);
        }

        return [.. output];
    }

    public static byte[] NatsSubjectStrToMqttTopic(string subject)
    {
        ArgumentNullException.ThrowIfNull(subject);
        return NatsSubjectToMqttTopic(System.Text.Encoding.UTF8.GetBytes(subject));
    }

    public static byte[] NatsSubjectToMqttTopic(ReadOnlySpan<byte> subject)
    {
        var topic = new List<byte>(subject.Length);

        for (var i = 0; i < subject.Length; i++)
        {
            switch (subject[i])
            {
                case Slash when i < subject.Length - 1:
                {
                    var next = subject[i + 1];
                    if (next == Dot)
                    {
                        topic.Add(Slash);
                        i++;
                    }
                    else if (next == Slash)
                    {
                        topic.Add(Dot);
                        i++;
                    }

                    break;
                }
                case Dot:
                    topic.Add(Slash);
                    break;
                default:
                    topic.Add(subject[i]);
                    break;
            }
        }

        return [.. topic];
    }

    public static bool MqttNeedSubForLevelUp(string subject)
    {
        if (subject.Length < 3)
        {
            return false;
        }

        return subject[^2] == '.' && subject[^1] == '>';
    }
}
