// Ports MQTT topic/subject conversion behavior from Go reference:
// golang/nats-server/server/mqtt_test.go — TestMQTTTopicAndSubjectConversion,
// TestMQTTFilterConversion, TestMQTTTopicWithDot, TestMQTTSubjectWildcardStart
// golang/nats-server/server/mqtt.go — mqttTopicToNATSPubSubject, mqttFilterToNATSSubject,
// natsSubjectToMQTTTopic, mqttToNATSSubjectConversion

namespace NATS.Server.Mqtt.Tests.Mqtt;

/// <summary>
/// Tests MQTT topic to NATS subject conversion and vice versa, porting the
/// Go TestMQTTTopicAndSubjectConversion and TestMQTTFilterConversion tests.
/// These are pure-logic conversion tests -- no server needed.
/// </summary>
public class MqttTopicMappingParityTests
{
    // -------------------------------------------------------------------------
    // Helper: MQTT topic -> NATS subject conversion
    // Mirrors Go: mqttTopicToNATSPubSubject / mqttToNATSSubjectConversion(mt, false)
    // -------------------------------------------------------------------------

    private static string MqttTopicToNatsSubject(string mqttTopic)
    {
        var mt = mqttTopic.AsSpan();
        var res = new List<char>(mt.Length + 10);

        var end = mt.Length - 1;
        for (var i = 0; i < mt.Length; i++)
        {
            switch (mt[i])
            {
                case '/':
                    if (i == 0 || (res.Count > 0 && res[^1] == '.'))
                    {
                        res.Add('/');
                        res.Add('.');
                    }
                    else if (i == end || mt[i + 1] == '/')
                    {
                        res.Add('.');
                        res.Add('/');
                    }
                    else
                    {
                        res.Add('.');
                    }

                    break;
                case ' ':
                    throw new FormatException("spaces not supported in MQTT topic");
                case '.':
                    res.Add('/');
                    res.Add('/');
                    break;
                case '+':
                case '#':
                    throw new FormatException("wildcards not allowed in publish topic");
                default:
                    res.Add(mt[i]);
                    break;
            }
        }

        if (res.Count > 0 && res[^1] == '.')
        {
            res.Add('/');
        }

        return new string(res.ToArray());
    }

    // -------------------------------------------------------------------------
    // Helper: MQTT filter -> NATS subject conversion (wildcards allowed)
    // Mirrors Go: mqttFilterToNATSSubject / mqttToNATSSubjectConversion(filter, true)
    // -------------------------------------------------------------------------

    private static string MqttFilterToNatsSubject(string mqttFilter)
    {
        var mt = mqttFilter.AsSpan();
        var res = new List<char>(mt.Length + 10);

        var end = mt.Length - 1;
        for (var i = 0; i < mt.Length; i++)
        {
            switch (mt[i])
            {
                case '/':
                    if (i == 0 || (res.Count > 0 && res[^1] == '.'))
                    {
                        res.Add('/');
                        res.Add('.');
                    }
                    else if (i == end || mt[i + 1] == '/')
                    {
                        res.Add('.');
                        res.Add('/');
                    }
                    else
                    {
                        res.Add('.');
                    }

                    break;
                case ' ':
                    throw new FormatException("spaces not supported in MQTT topic");
                case '.':
                    res.Add('/');
                    res.Add('/');
                    break;
                case '+':
                    res.Add('*');
                    break;
                case '#':
                    res.Add('>');
                    break;
                default:
                    res.Add(mt[i]);
                    break;
            }
        }

        if (res.Count > 0 && res[^1] == '.')
        {
            res.Add('/');
        }

        return new string(res.ToArray());
    }

    // -------------------------------------------------------------------------
    // Helper: NATS subject -> MQTT topic conversion
    // Mirrors Go: natsSubjectToMQTTTopic
    // -------------------------------------------------------------------------

    private static string NatsSubjectToMqttTopic(string natsSubject)
    {
        var subject = natsSubject.AsSpan();
        var topic = new char[subject.Length];
        var end = subject.Length - 1;
        var j = 0;
        for (var i = 0; i < subject.Length; i++)
        {
            switch (subject[i])
            {
                case '/':
                    if (i < end)
                    {
                        var c = subject[i + 1];
                        if (c == '.' || c == '/')
                        {
                            topic[j] = c == '.' ? '/' : '.';
                            j++;
                            i++;
                        }
                    }

                    break;
                case '.':
                    topic[j] = '/';
                    j++;
                    break;
                default:
                    topic[j] = subject[i];
                    j++;
                    break;
            }
        }

        return new string(topic, 0, j);
    }

    // =========================================================================
    // Go: TestMQTTTopicAndSubjectConversion server/mqtt_test.go:1779
    // =========================================================================

    [Theory]
    [InlineData("/", "/./")]
    [InlineData("//", "/././")]
    [InlineData("///", "/./././")]
    [InlineData("////", "/././././")]
    [InlineData("foo", "foo")]
    [InlineData("/foo", "/.foo")]
    [InlineData("//foo", "/./.foo")]
    [InlineData("///foo", "/././.foo")]
    [InlineData("///foo/", "/././.foo./")]
    [InlineData("///foo//", "/././.foo././")]
    [InlineData("///foo///", "/././.foo./././")]
    [InlineData("//.foo.//", "/././/foo//././")]
    [InlineData("foo/bar", "foo.bar")]
    [InlineData("/foo/bar", "/.foo.bar")]
    [InlineData("/foo/bar/", "/.foo.bar./")]
    [InlineData("foo/bar/baz", "foo.bar.baz")]
    [InlineData("/foo/bar/baz", "/.foo.bar.baz")]
    [InlineData("/foo/bar/baz/", "/.foo.bar.baz./")]
    [InlineData("bar/", "bar./")]
    [InlineData("bar//", "bar././")]
    [InlineData("bar///", "bar./././")]
    [InlineData("foo//bar", "foo./.bar")]
    [InlineData("foo///bar", "foo././.bar")]
    [InlineData("foo////bar", "foo./././.bar")]
    [InlineData(".", "//")]
    [InlineData("..", "////")]
    [InlineData("...", "//////")]
    [InlineData("./", "//./")]
    [InlineData(".//.", "//././/")]
    [InlineData("././.", "//.//.//")]
    [InlineData("././/.", "//.//././/")]
    [InlineData(".foo", "//foo")]
    [InlineData("foo.", "foo//")]
    [InlineData(".foo.", "//foo//")]
    [InlineData("foo../bar/", "foo////.bar./")]
    [InlineData("foo../bar/.", "foo////.bar.//")]
    [InlineData("/foo/", "/.foo./")]
    [InlineData("./foo/.", "//.foo.//")]
    [InlineData("foo.bar/baz", "foo//bar.baz")]
    public void Topic_to_nats_subject_converts_correctly(string mqttTopic, string expectedNatsSubject)
    {
        // Go: mqttTopicToNATSPubSubject server/mqtt_test.go:1779
        var natsSubject = MqttTopicToNatsSubject(mqttTopic);
        natsSubject.ShouldBe(expectedNatsSubject);
    }

    [Theory]
    [InlineData("/", "/./")]
    [InlineData("//", "/././")]
    [InlineData("foo", "foo")]
    [InlineData("foo/bar", "foo.bar")]
    [InlineData("/foo/bar", "/.foo.bar")]
    [InlineData(".", "//")]
    [InlineData(".foo", "//foo")]
    [InlineData("foo.", "foo//")]
    [InlineData("foo.bar/baz", "foo//bar.baz")]
    [InlineData("foo//bar", "foo./.bar")]
    [InlineData("/foo/", "/.foo./")]
    public void Topic_round_trips_through_nats_subject_and_back(string mqttTopic, string natsSubject)
    {
        // Go: TestMQTTTopicAndSubjectConversion verifies round-trip server/mqtt_test.go:1843
        var converted = MqttTopicToNatsSubject(mqttTopic);
        converted.ShouldBe(natsSubject);

        var backToMqtt = NatsSubjectToMqttTopic(converted);
        backToMqtt.ShouldBe(mqttTopic);
    }

    [Theory]
    [InlineData("foo/+", "wildcards not allowed")]
    [InlineData("foo/#", "wildcards not allowed")]
    [InlineData("foo bar", "not supported")]
    public void Topic_to_nats_subject_rejects_invalid_topics(string mqttTopic, string expectedErrorSubstring)
    {
        // Go: TestMQTTTopicAndSubjectConversion error cases server/mqtt_test.go:1826
        var ex = Should.Throw<FormatException>(() => MqttTopicToNatsSubject(mqttTopic));
        ex.Message.ShouldContain(expectedErrorSubstring, Case.Insensitive);
    }

    // =========================================================================
    // Go: TestMQTTFilterConversion server/mqtt_test.go:1852
    // =========================================================================

    [Theory]
    [InlineData("+", "*")]
    [InlineData("/+", "/.*")]
    [InlineData("+/", "*./")]
    [InlineData("/+/", "/.*./")]
    [InlineData("foo/+", "foo.*")]
    [InlineData("foo/+/", "foo.*./")]
    [InlineData("foo/+/bar", "foo.*.bar")]
    [InlineData("foo/+/+", "foo.*.*")]
    [InlineData("foo/+/+/", "foo.*.*./")]
    [InlineData("foo/+/+/bar", "foo.*.*.bar")]
    [InlineData("foo//+", "foo./.*")]
    [InlineData("foo//+/", "foo./.*./")]
    [InlineData("foo//+//", "foo./.*././")]
    [InlineData("foo//+//bar", "foo./.*./.bar")]
    [InlineData("foo///+///bar", "foo././.*././.bar")]
    [InlineData("foo.bar///+///baz", "foo//bar././.*././.baz")]
    public void Filter_single_level_wildcard_converts_plus_to_star(string mqttFilter, string expectedNatsSubject)
    {
        // Go: TestMQTTFilterConversion single level wildcard server/mqtt_test.go:1860
        var natsSubject = MqttFilterToNatsSubject(mqttFilter);
        natsSubject.ShouldBe(expectedNatsSubject);
    }

    [Theory]
    [InlineData("#", ">")]
    [InlineData("/#", "/.>")]
    [InlineData("/foo/#", "/.foo.>")]
    [InlineData("foo/#", "foo.>")]
    [InlineData("foo//#", "foo./.>")]
    [InlineData("foo///#", "foo././.>")]
    [InlineData("foo/bar/#", "foo.bar.>")]
    [InlineData("foo/bar.baz/#", "foo.bar//baz.>")]
    public void Filter_multi_level_wildcard_converts_hash_to_greater_than(string mqttFilter, string expectedNatsSubject)
    {
        // Go: TestMQTTFilterConversion multi level wildcard server/mqtt_test.go:1877
        var natsSubject = MqttFilterToNatsSubject(mqttFilter);
        natsSubject.ShouldBe(expectedNatsSubject);
    }

    // =========================================================================
    // Go: TestMQTTTopicWithDot server/mqtt_test.go:7674
    // =========================================================================

    [Theory]
    [InlineData("foo//bar", "foo.bar")]
    [InlineData("//foo", ".foo")]
    [InlineData("foo//", "foo.")]
    [InlineData("//", ".")]
    public void Nats_subject_with_slash_slash_converts_to_mqtt_dot(string natsSubject, string expectedMqttTopic)
    {
        // Go: natsSubjectToMQTTTopic converts '//' back to '.'
        var mqttTopic = NatsSubjectToMqttTopic(natsSubject);
        mqttTopic.ShouldBe(expectedMqttTopic);
    }

    [Fact]
    public void Nats_subject_dot_becomes_mqtt_topic_slash()
    {
        // Go: basic '.' -> '/' conversion
        var result = NatsSubjectToMqttTopic("foo.bar.baz");
        result.ShouldBe("foo/bar/baz");
    }

    // =========================================================================
    // Additional conversion edge cases
    // =========================================================================

    [Fact]
    public void Empty_topic_converts_to_empty_subject()
    {
        var result = MqttTopicToNatsSubject(string.Empty);
        result.ShouldBe(string.Empty);
    }

    [Fact]
    public void Single_character_topic_converts_identity()
    {
        var result = MqttTopicToNatsSubject("a");
        result.ShouldBe("a");
    }

    [Fact]
    public void Nats_subject_to_mqtt_topic_simple_passes_through()
    {
        var result = NatsSubjectToMqttTopic("foo");
        result.ShouldBe("foo");
    }

    [Fact]
    public void Filter_conversion_preserves_mixed_wildcards()
    {
        var result = MqttFilterToNatsSubject("+/foo/#");
        result.ShouldBe("*.foo.>");
    }

    [Theory]
    [InlineData("+", "*")]
    [InlineData("+/foo", "*.foo")]
    [InlineData("+/+", "*.*")]
    [InlineData("#", ">")]
    public void Filter_starting_with_wildcard_converts_correctly(string mqttFilter, string expectedNatsSubject)
    {
        // Go: TestMQTTSubjectWildcardStart server/mqtt_test.go:7552
        var result = MqttFilterToNatsSubject(mqttFilter);
        result.ShouldBe(expectedNatsSubject);
    }

    // =========================================================================
    // Go: TestMQTTPublishTopicErrors server/mqtt_test.go:4084
    // =========================================================================

    [Theory]
    [InlineData("foo/+")]
    [InlineData("foo/#")]
    public void Publish_topic_with_wildcards_throws(string mqttTopic)
    {
        Should.Throw<FormatException>(() => MqttTopicToNatsSubject(mqttTopic));
    }

    [Fact]
    public void Publish_topic_with_space_throws()
    {
        Should.Throw<FormatException>(() => MqttTopicToNatsSubject("foo bar"));
    }
}
