package membership_test

import (
	"path/filepath"
	"testing"
	"time"

	"github.com/enmanuel/unibus/pkg/blobstore"
	"github.com/enmanuel/unibus/pkg/client"
	"github.com/enmanuel/unibus/pkg/frame"
	"github.com/enmanuel/unibus/pkg/membership"
	"github.com/enmanuel/unibus/pkg/room"
)

// TestClientCreateRoomRefreshPublishFlow is the issue 0006e DoD: under enforce+ACL
// a peer creates a room AFTER connecting, and pub/sub works without manual
// intervention because the client follows the membership-change contract
// (CreateRoom -> RefreshSession -> Subscribe/Publish), exactly as cmd/chat and
// cmd/worker now do. This is the end-to-end flow through the client API, proving
// the ACL is usable under enforce rather than something an operator must disable.
func TestClientCreateRoomRefreshPublishFlow(t *testing.T) {
	dir := t.TempDir()
	store, err := membership.Open(filepath.Join(dir, "unibus.db"))
	if err != nil {
		t.Fatalf("store: %v", err)
	}
	t.Cleanup(func() { store.Close() })

	alice, bob := mustID(t), mustID(t)
	mustAddUser(t, store, alice, "alice")
	mustAddUser(t, store, bob, "bob")

	srv := startACLNats(t, store) // data plane: enforce + per-subject ACL
	blobs, _ := blobstore.New(filepath.Join(dir, "blobs"))
	ctrl := newCtrl(t, store, blobs)

	aliceC, err := client.NewWithOptions(srv.ClientURL(), ctrl, alice, client.Options{UseNkey: true})
	if err != nil {
		t.Fatalf("connect alice: %v", err)
	}
	defer aliceC.Close()
	bobC, err := client.NewWithOptions(srv.ClientURL(), ctrl, bob, client.Options{UseNkey: true})
	if err != nil {
		t.Fatalf("connect bob: %v", err)
	}
	defer bobC.Close()

	// alice creates a room AFTER connecting: the subject was not in her ACL at
	// connect time, so she must refresh to publish on it (the worker contract).
	roomID, err := aliceC.CreateRoom("room.flow.x", room.ModeNATS)
	if err != nil {
		t.Fatalf("alice create room: %v", err)
	}
	if err := aliceC.RefreshSession(); err != nil {
		t.Fatalf("alice refresh: %v", err)
	}

	// alice invites bob; bob joins then refreshes to gain the subject (the chat
	// subscriber contract), and only then subscribes.
	if err := aliceC.Invite(roomID, bobC.Endpoint()); err != nil {
		t.Fatalf("alice invite bob: %v", err)
	}
	if err := bobC.Join(roomID); err != nil {
		t.Fatalf("bob join: %v", err)
	}
	if err := bobC.RefreshSession(); err != nil {
		t.Fatalf("bob refresh: %v", err)
	}
	got := make(chan string, 4)
	sub, err := bobC.Subscribe(roomID, func(_ frame.Frame, plaintext []byte) { got <- string(plaintext) })
	if err != nil {
		t.Fatalf("bob subscribe after refresh: %v", err)
	}
	defer sub.Unsubscribe()
	time.Sleep(200 * time.Millisecond) // let the subscription settle

	if err := aliceC.Publish(roomID, []byte("hello-under-acl")); err != nil {
		t.Fatalf("alice publish after refresh: %v", err)
	}
	select {
	case msg := <-got:
		if msg != "hello-under-acl" {
			t.Fatalf("bob got %q", msg)
		}
	case <-time.After(3 * time.Second):
		t.Fatalf("bob did not receive the message: the create->refresh->subscribe flow is broken under enforce+ACL")
	}
}
