// 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
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Adapted from server/mqtt.go in the NATS server Go source.

using System.Text;
using System.Text.Json;

namespace ZB.MOM.NatsNet.Server.Mqtt;

// ============================================================================
// MqttHelpers — Standalone helpers (Sub-batch F, features 2264-2404)
// ============================================================================

/// <summary>
/// Static helper methods for MQTT protocol processing.
/// Mirrors various free functions and helper methods in server/mqtt.go.
/// </summary>
internal static class MqttHelpers
{
    // ------------------------------------------------------------------
    // Feature 2264: mqttIsReservedSubscription
    // ------------------------------------------------------------------

    /// <summary>
    /// Returns <c>true</c> if the given topic filter is a reserved MQTT topic
    /// (i.e. starts with <c>$</c> or begins with <see cref="MqttTopics.SubPrefix"/>).
    /// Mirrors Go <c>mqttIsReservedSubscription()</c> in server/mqtt.go.
    /// </summary>
    public static bool IsMqttReservedSubscription(string filter)
    {
        if (string.IsNullOrEmpty(filter)) return false;
        return filter[0] == '$' || filter.StartsWith(MqttTopics.SubPrefix, StringComparison.Ordinal);
    }

    // ------------------------------------------------------------------
    // Feature 2265: mqttDecodeRetainedMessage
    // ------------------------------------------------------------------

    /// <summary>
    /// Decodes a retained message from the JetStream stored message headers and body.
    /// Returns <c>null</c> if the message cannot be decoded.
    /// Mirrors Go <c>mqttDecodeRetainedMessage()</c> in server/mqtt.go.
    /// </summary>
    public static MqttRetainedMsg? DecodeRetainedMessage(string subject, byte[]? hdr, byte[]? body)
    {
        if (string.IsNullOrEmpty(subject)) return null;

        // Attempt JSON deserialization first (modern format).
        if (body != null && body.Length > 0)
        {
            try
            {
                var rm = JsonSerializer.Deserialize<MqttRetainedMsg>(body);
                if (rm != null)
                {
                    // Fill in the subject from the NATS subject if not in body.
                    if (string.IsNullOrEmpty(rm.Subject))
                        rm.Subject = subject;
                    if (string.IsNullOrEmpty(rm.Topic))
                        rm.Topic = Encoding.UTF8.GetString(MqttSubjectConverter.NatsSubjectStrToMqttTopic(subject));
                    return rm;
                }
            }
            catch
            {
                // Fall through to header-based decode.
            }
        }

        // Legacy: topic encoded in NATS subject via subject converter.
        byte[] topicBytes = MqttSubjectConverter.NatsSubjectStrToMqttTopic(subject);
        string topic = topicBytes.Length > 0 ? Encoding.UTF8.GetString(topicBytes) : string.Empty;
        if (string.IsNullOrEmpty(topic)) return null;

        // Parse QoS flag and origin from NATS message headers.
        byte flags  = 0;
        string origin = string.Empty;

        if (hdr != null && hdr.Length > 0)
        {
            string hdrStr = Encoding.UTF8.GetString(hdr);
            foreach (var line in hdrStr.Split('\n', StringSplitOptions.RemoveEmptyEntries))
            {
                var trimmed = line.Trim();
                if (trimmed.StartsWith(MqttTopics.NatsRetainedMessageOrigin + ":", StringComparison.OrdinalIgnoreCase))
                    origin = trimmed[(MqttTopics.NatsRetainedMessageOrigin.Length + 1)..].Trim();
                else if (trimmed.StartsWith(MqttTopics.NatsRetainedMessageFlags + ":", StringComparison.OrdinalIgnoreCase))
                    _ = byte.TryParse(trimmed[(MqttTopics.NatsRetainedMessageFlags.Length + 1)..].Trim(), out flags);
            }
        }

        return new MqttRetainedMsg
        {
            Origin  = origin,
            Subject = subject,
            Topic   = topic,
            Msg     = body,
            Flags   = flags,
        };
    }

    // ------------------------------------------------------------------
    // Feature 2266: mqttGeneratePubPerms
    // ------------------------------------------------------------------

    /// <summary>
    /// Generates a publish-permissions filter for retained-message authorization.
    /// Mirrors Go <c>mqttGeneratePubPerms()</c> in server/mqtt.go.
    /// </summary>
    public static MqttPubPerms GeneratePubPerms(
        IEnumerable<string>? allow,
        IEnumerable<string>? deny)
    {
        var perms = new MqttPubPerms();
        if (allow != null)
        {
            foreach (var a in allow)
            {
                if (a == ">")
                    perms.AllowAll = true;
                else
                    perms.Allow.Add(a);
            }
        }
        if (deny != null)
        {
            foreach (var d in deny)
                perms.Deny.Add(d);
        }
        return perms;
    }

    // ------------------------------------------------------------------
    // Feature 2267: mqttCheckPubRetainedPerms
    // ------------------------------------------------------------------

    /// <summary>
    /// Checks whether the subject is permitted for retained-message publishing.
    /// Mirrors Go <c>mqttCheckPubRetainedPerms()</c> in server/mqtt.go.
    /// </summary>
    public static bool CheckPubRetainedPerms(MqttPubPerms perms, string subject)
    {
        if (perms.AllowAll)
        {
            // Check deny list.
            return !perms.Deny.Contains(subject);
        }
        if (!perms.Allow.Contains(subject)) return false;
        return !perms.Deny.Contains(subject);
    }

    // ------------------------------------------------------------------
    // Feature 2268: mqttTopicFilter / helper utilities
    // ------------------------------------------------------------------

    /// <summary>
    /// Returns <c>true</c> if the topic filter contains wildcard characters.
    /// Mirrors Go <c>mqttTopicFilterContainsWildcard()</c>.
    /// </summary>
    public static bool TopicFilterContainsWildcard(string filter) =>
        filter.Contains('+') || filter.Contains('#');

    /// <summary>
    /// Returns the MQTT topic as a NATS subject (for publish/subscribe).
    /// Thin wrapper around <see cref="MqttSubjectConverter.MqttTopicToNatsPubSubject"/>.
    /// </summary>
    public static string TopicToNatsSubject(string topic)
    {
        byte[] subjBytes = MqttSubjectConverter.MqttTopicToNatsPubSubject(
            Encoding.UTF8.GetBytes(topic));
        return Encoding.UTF8.GetString(subjBytes);
    }
}
