using NATS.Server.Mqtt;

namespace NATS.Server.Mqtt.Tests;

/// <summary>
/// Tests for MqttTopicMapper with full Go parity including dots in topics,
/// empty levels, '$' prefix protection, and leading/trailing slashes.
/// Go reference: mqtt.go mqttToNATSSubjectConversion ~line 2200.
/// </summary>
public class MqttTopicMapperTests
{
    // -----------------------------------------------------------------------
    // MqttToNats — basic mapping
    // -----------------------------------------------------------------------

    [Theory]
    [InlineData("a/b/c", "a.b.c")]
    [InlineData("sensor/temp", "sensor.temp")]
    [InlineData("home/living/light", "home.living.light")]
    public void MqttToNats_separator_mapping(string mqtt, string nats)
    {
        MqttTopicMapper.MqttToNats(mqtt).ShouldBe(nats);
    }

    [Theory]
    [InlineData("+", "*")]
    [InlineData("sensor/+", "sensor.*")]
    [InlineData("+/temp", "*.temp")]
    [InlineData("+/+/+", "*.*.*")]
    public void MqttToNats_single_level_wildcard(string mqtt, string nats)
    {
        MqttTopicMapper.MqttToNats(mqtt).ShouldBe(nats);
    }

    [Theory]
    [InlineData("#", ">")]
    [InlineData("sensor/#", "sensor.>")]
    [InlineData("home/+/#", "home.*.>")]
    public void MqttToNats_multi_level_wildcard(string mqtt, string nats)
    {
        MqttTopicMapper.MqttToNats(mqtt).ShouldBe(nats);
    }

    [Fact]
    public void MqttToNats_empty_string()
    {
        MqttTopicMapper.MqttToNats("").ShouldBe("");
    }

    // -----------------------------------------------------------------------
    // MqttToNats — dot escaping (Go parity)
    // -----------------------------------------------------------------------

    [Theory]
    [InlineData("a.b/c", "a_DOT_b.c")]
    [InlineData("host.name/metric", "host_DOT_name.metric")]
    [InlineData("a.b.c", "a_DOT_b_DOT_c")]
    public void MqttToNats_dots_in_topic_are_escaped(string mqtt, string nats)
    {
        MqttTopicMapper.MqttToNats(mqtt).ShouldBe(nats);
    }

    // -----------------------------------------------------------------------
    // MqttToNats — empty levels (leading/trailing/consecutive slashes)
    // -----------------------------------------------------------------------

    [Theory]
    [InlineData("/a/b", ".a.b")]
    [InlineData("a/b/", "a.b.")]
    [InlineData("a//b", "a..b")]
    [InlineData("//", "..")]
    public void MqttToNats_empty_levels(string mqtt, string nats)
    {
        MqttTopicMapper.MqttToNats(mqtt).ShouldBe(nats);
    }

    // -----------------------------------------------------------------------
    // NatsToMqtt — reverse mapping
    // -----------------------------------------------------------------------

    [Theory]
    [InlineData("a.b.c", "a/b/c")]
    [InlineData("sensor.temp", "sensor/temp")]
    [InlineData("*", "+")]
    [InlineData(">", "#")]
    [InlineData("sensor.*", "sensor/+")]
    [InlineData("sensor.>", "sensor/#")]
    public void NatsToMqtt_basic_reverse(string nats, string mqtt)
    {
        MqttTopicMapper.NatsToMqtt(nats).ShouldBe(mqtt);
    }

    [Fact]
    public void NatsToMqtt_empty_string()
    {
        MqttTopicMapper.NatsToMqtt("").ShouldBe("");
    }

    [Theory]
    [InlineData("a_DOT_b.c", "a.b/c")]
    [InlineData("host_DOT_name.metric", "host.name/metric")]
    public void NatsToMqtt_dot_escape_reversed(string nats, string mqtt)
    {
        MqttTopicMapper.NatsToMqtt(nats).ShouldBe(mqtt);
    }

    // -----------------------------------------------------------------------
    // Round-trip: MqttToNats → NatsToMqtt should be identity
    // -----------------------------------------------------------------------

    [Theory]
    [InlineData("a/b/c")]
    [InlineData("sensor/+/data")]
    [InlineData("home/#")]
    [InlineData("a.b/c.d")]
    [InlineData("/leading")]
    [InlineData("trailing/")]
    [InlineData("a//b")]
    public void RoundTrip_mqtt_to_nats_and_back(string mqtt)
    {
        var nats = MqttTopicMapper.MqttToNats(mqtt);
        var roundTripped = MqttTopicMapper.NatsToMqtt(nats);
        roundTripped.ShouldBe(mqtt);
    }

    // -----------------------------------------------------------------------
    // Dollar topic protection (MQTT spec [MQTT-4.7.2-1])
    // -----------------------------------------------------------------------

    [Fact]
    public void IsDollarTopic_detects_system_topics()
    {
        MqttTopicMapper.IsDollarTopic("$SYS/info").ShouldBeTrue();
        MqttTopicMapper.IsDollarTopic("$share/group/topic").ShouldBeTrue();
        MqttTopicMapper.IsDollarTopic("normal/topic").ShouldBeFalse();
        MqttTopicMapper.IsDollarTopic("").ShouldBeFalse();
    }

    [Fact]
    public void WildcardMatchesDollarTopic_enforces_spec()
    {
        // Wildcard filters should NOT match $ topics
        MqttTopicMapper.WildcardMatchesDollarTopic("#", "$SYS/info").ShouldBeFalse();
        MqttTopicMapper.WildcardMatchesDollarTopic("+/info", "$SYS/info").ShouldBeFalse();

        // Explicit $ filters match $ topics
        MqttTopicMapper.WildcardMatchesDollarTopic("$SYS/#", "$SYS/info").ShouldBeTrue();
        MqttTopicMapper.WildcardMatchesDollarTopic("$SYS/+", "$SYS/info").ShouldBeTrue();

        // Non-$ topics always matchable
        MqttTopicMapper.WildcardMatchesDollarTopic("#", "normal/topic").ShouldBeTrue();
        MqttTopicMapper.WildcardMatchesDollarTopic("+/topic", "normal/topic").ShouldBeTrue();
    }
}
