package events

import (
	"fmt"
	"testing"

	panelv1 "github.com/dbledeez/panel/proto/panel/v1"
)

func stateEvent(instanceID string) *panelv1.Event {
	return &panelv1.Event{Payload: &panelv1.Event_InstanceState{
		InstanceState: &panelv1.InstanceStateUpdate{InstanceId: instanceID},
	}}
}

func TestRingMonotonicIDsAndSince(t *testing.T) {
	r := newReplayRing(8)
	for i := 0; i < 5; i++ {
		id := r.append(stateEvent(fmt.Sprintf("i%d", i)))
		if want := uint64(i + 1); id != want {
			t.Fatalf("append id = %d, want %d", id, want)
		}
	}
	if r.lastID() != 5 {
		t.Fatalf("lastID = %d, want 5", r.lastID())
	}
	out, ok := r.since(2)
	if !ok {
		t.Fatalf("since(2) reported a gap on an unfilled ring")
	}
	if len(out) != 3 || out[0].ID != 3 || out[2].ID != 5 {
		t.Fatalf("since(2) = %+v, want ids 3..5", out)
	}
	// Fully caught up → empty, gapless.
	if out, ok := r.since(5); len(out) != 0 || !ok {
		t.Fatalf("since(lastID) = (%d events, ok=%v), want (0, true)", len(out), ok)
	}
}

func TestRingOverflowReportsGap(t *testing.T) {
	r := newReplayRing(4)
	for i := 0; i < 10; i++ { // ids 1..10; ring retains 7..10
		r.append(stateEvent("x"))
	}
	out, ok := r.since(3)
	if ok {
		t.Fatalf("since(3) after overflow should report a gap")
	}
	if len(out) != 4 || out[0].ID != 7 || out[3].ID != 10 {
		t.Fatalf("since(3) = %d events (first=%d), want retained suffix 7..10", len(out), out[0].ID)
	}
	// Resume point exactly at oldest-1 is gapless.
	if _, ok := r.since(6); !ok {
		t.Fatalf("since(oldest-1) should be gapless")
	}
}

func TestBusReplayFiltersAndStampedSubscribe(t *testing.T) {
	b := New(16)
	sub := b.SubscribeStamped(Filter{InstanceID: "a"})
	defer sub.Close()

	b.Publish(stateEvent("a")) // id 1
	b.Publish(stateEvent("b")) // id 2
	b.Publish(stateEvent("a")) // id 3

	// Stamped subscription only sees matching events, with real ids.
	got1 := <-sub.Stamped()
	got2 := <-sub.Stamped()
	if got1.ID != 1 || got2.ID != 3 {
		t.Fatalf("stamped ids = %d,%d, want 1,3", got1.ID, got2.ID)
	}

	// Replay applies the same filter.
	out, ok := b.Replay(0, Filter{InstanceID: "a"})
	if !ok || len(out) != 2 || out[0].ID != 1 || out[1].ID != 3 {
		t.Fatalf("Replay = %+v ok=%v, want ids 1,3 gapless", out, ok)
	}
	if b.LastID() != 3 {
		t.Fatalf("LastID = %d, want 3", b.LastID())
	}
}

func TestBusDroppedTotal(t *testing.T) {
	b := New(1)
	sub := b.Subscribe(Filter{})
	defer sub.Close()
	b.Publish(stateEvent("a")) // fills the 1-slot buffer
	b.Publish(stateEvent("a")) // dropped
	if b.DroppedTotal() != 1 {
		t.Fatalf("DroppedTotal = %d, want 1", b.DroppedTotal())
	}
}
