using NSubstitute;
using NATS.Server.JetStream.Storage;
using NATS.Server.Mqtt;

namespace NATS.Server.Mqtt.Tests;

// Go reference: server/mqtt.go ($MQTT_msgs, $MQTT_sess, $MQTT_rmsgs JetStream streams)

public class MqttPersistenceTests
{
    [Fact]
    public async Task Session_persists_across_restart()
    {
        // Go reference: server/mqtt.go mqttStoreSession — session survives restart
        var store = MqttSessionStoreTestHelper.CreateWithJetStream();

        await store.ConnectAsync("client-1", cleanSession: false);
        store.AddSubscription("client-1", "topic/test", qos: 1);
        await store.SaveSessionAsync("client-1");

        // Simulate restart — new store backed by the same IStreamStore
        var recovered = MqttSessionStoreTestHelper.CreateWithJetStream(store.BackingStore!);
        await recovered.ConnectAsync("client-1", cleanSession: false);

        var subs = recovered.GetSubscriptions("client-1");
        subs.ShouldContainKey("topic/test");
    }

    [Fact]
    public async Task Clean_session_deletes_existing()
    {
        // Go reference: server/mqtt.go cleanSession=true deletes saved state
        var store = MqttSessionStoreTestHelper.CreateWithJetStream();

        await store.ConnectAsync("client-2", cleanSession: false);
        store.AddSubscription("client-2", "persist/me", qos: 1);
        await store.SaveSessionAsync("client-2");

        // Reconnect with clean session
        await store.ConnectAsync("client-2", cleanSession: true);

        var subs = store.GetSubscriptions("client-2");
        subs.ShouldBeEmpty();
    }

    [Fact]
    public async Task Retained_message_survives_restart()
    {
        // Go reference: server/mqtt.go retained message persistence via JetStream
        var retained = MqttRetainedStoreTestHelper.CreateWithJetStream();

        await retained.SetRetainedAsync("sensors/temp", "72.5"u8.ToArray());

        // Simulate restart
        var recovered = MqttRetainedStoreTestHelper.CreateWithJetStream(retained.BackingStore!);
        var msg = await recovered.GetRetainedAsync("sensors/temp");

        msg.ShouldNotBeNull();
        System.Text.Encoding.UTF8.GetString(msg).ShouldBe("72.5");
    }

    [Fact]
    public async Task Retained_message_cleared_with_empty_payload()
    {
        // Go reference: server/mqtt.go empty payload clears retained
        var retained = MqttRetainedStoreTestHelper.CreateWithJetStream();

        await retained.SetRetainedAsync("sensors/temp", "72.5"u8.ToArray());
        await retained.SetRetainedAsync("sensors/temp", ReadOnlyMemory<byte>.Empty); // clear

        var msg = await retained.GetRetainedAsync("sensors/temp");
        msg.ShouldBeNull();
    }
}

public static class MqttSessionStoreTestHelper
{
    public static MqttSessionStore CreateWithJetStream(IStreamStore? backingStore = null)
    {
        var store = backingStore ?? new MemStore();
        return new MqttSessionStore(store);
    }
}

public static class MqttRetainedStoreTestHelper
{
    public static MqttRetainedStore CreateWithJetStream(IStreamStore? backingStore = null)
    {
        var store = backingStore ?? new MemStore();
        return new MqttRetainedStore(store);
    }
}
