package notify

import (
	"context"
	"testing"
	"time"

	"github.com/google/uuid"
)

func TestStreamHubSubscribeReceivesPublish(t *testing.T) {
	hub := NewStreamHub()
	uid := uuid.New()
	ch, cancel := hub.Subscribe(uid)
	defer cancel()

	want := Message{ID: uuid.New(), UserID: uid, Topic: "t", Title: "hi"}
	hub.Publish(uid, want)

	select {
	case got := <-ch:
		if got.ID != want.ID {
			t.Fatalf("got %v, want %v", got.ID, want.ID)
		}
	case <-time.After(time.Second):
		t.Fatal("did not receive published message")
	}
}

func TestStreamHubUnsubscribeStopsDelivery(t *testing.T) {
	hub := NewStreamHub()
	uid := uuid.New()
	ch, cancel := hub.Subscribe(uid)
	cancel() // immediate unsubscribe closes the channel

	// channel should be closed, draining returns zero value + !ok.
	if _, ok := <-ch; ok {
		t.Fatal("expected channel closed after cancel")
	}
	// Publishing to an unsubscribed user must not panic.
	hub.Publish(uid, Message{UserID: uid})
}

func TestStreamHubDropsSlowSubscriber(t *testing.T) {
	hub := NewStreamHub()
	uid := uuid.New()
	ch, cancel := hub.Subscribe(uid)
	defer cancel()

	// Flood beyond buffer; Publish must not block / deadlock.
	done := make(chan struct{})
	go func() {
		for i := 0; i < streamSubBuffer*4; i++ {
			hub.Publish(uid, Message{UserID: uid, Topic: "flood"})
		}
		close(done)
	}()
	select {
	case <-done:
	case <-time.After(2 * time.Second):
		t.Fatal("Publish deadlocked on slow subscriber")
	}
	// Buffer holds at most streamSubBuffer; the rest were dropped.
	count := 0
	for {
		select {
		case <-ch:
			count++
		default:
			if count > streamSubBuffer {
				t.Fatalf("buffered %d > cap %d (drop-on-slow violated)", count, streamSubBuffer)
			}
			return
		}
	}
}

func TestDispatcherPublishesAfterInsert(t *testing.T) {
	repo := &fakeRepo{}
	d := NewDispatcher(repo, newTestLogger())
	hub := NewStreamHub()
	d.SetStreamHub(hub)

	uid := uuid.New()
	ch, cancel := hub.Subscribe(uid)
	defer cancel()

	if err := d.Dispatch(context.Background(), Message{UserID: uid, Topic: "x", Title: "y"}); err != nil {
		t.Fatalf("dispatch: %v", err)
	}
	select {
	case got := <-ch:
		if got.Topic != "x" {
			t.Fatalf("topic = %q", got.Topic)
		}
	case <-time.After(time.Second):
		t.Fatal("hub did not receive dispatched message")
	}
}

func TestDispatcherNoPublishOnInsertFailure(t *testing.T) {
	repo := &fakeRepo{failNext: true}
	d := NewDispatcher(repo, newTestLogger())
	hub := NewStreamHub()
	d.SetStreamHub(hub)

	uid := uuid.New()
	ch, cancel := hub.Subscribe(uid)
	defer cancel()

	if err := d.Dispatch(context.Background(), Message{UserID: uid}); err == nil {
		t.Fatal("expected dispatch error on insert failure")
	}
	select {
	case <-ch:
		t.Fatal("hub received message despite insert failure")
	case <-time.After(100 * time.Millisecond):
		// expected: no publish.
	}
}
