package ws_test

import (
	"fmt"
	"sync"
	"testing"

	"github.com/owncord/server/ws"
)

// ─── Push ────────────────────────────────────────────────────────────────────

func TestPush_SingleEntry(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	rb.Push(1, 0, []byte("hello"))

	// afterSeq=0 is before the oldest seq (1), so EventsSince returns nil
	// (the buffer can't confirm it covers everything the caller missed).
	// Verify via OldestSeq and a valid afterSeq instead.
	if got := rb.OldestSeq(); got != 1 {
		t.Fatalf("expected oldest seq 1, got %d", got)
	}

	// afterSeq = oldestSeq means "give me everything after seq 1" = nothing newer.
	// But we can use EventsSince with afterSeq matching oldest to get items > oldest.
	// There's only seq 1, and 1 > 1 is false, so we get 0 events.
	got := rb.EventsSince(1)
	if len(got) != 0 {
		t.Fatalf("expected 0 events when afterSeq = only seq, got %d", len(got))
	}
}

func TestPush_MultipleInOrder(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	for i := uint64(1); i <= 5; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "msg-%d", i))
	}

	// afterSeq == oldestSeq (1) → nil (BUG-085: conservative boundary).
	if got := rb.EventsSince(1); got != nil {
		t.Fatalf("expected nil when afterSeq == oldestSeq, got %d events", len(got))
	}

	// afterSeq one past oldest → replay seq 3..5.
	got := rb.EventsSince(2)
	if len(got) != 3 {
		t.Fatalf("expected 3 events after seq 2, got %d", len(got))
	}
	for i, ev := range got {
		want := fmt.Sprintf("msg-%d", i+3)
		if string(ev) != want {
			t.Errorf("event[%d]: expected %q, got %q", i, want, string(ev))
		}
	}
}

func TestPush_WrapsAround(t *testing.T) {
	const cap = 4
	rb := ws.NewEventRingBuffer(cap)

	// Push 6 events into a buffer with capacity 4 — first two are evicted.
	for i := uint64(1); i <= 6; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "e%d", i))
	}

	got := rb.EventsSince(0)
	// afterSeq=0 is older than oldest (seq 3), so EventsSince returns nil.
	if got != nil {
		t.Fatalf("expected nil (afterSeq too old), got %d events", len(got))
	}

	// Ask for events after seq 2 — still too old.
	got = rb.EventsSince(2)
	if got != nil {
		t.Fatalf("expected nil (afterSeq 2 still evicted), got %d events", len(got))
	}

	// afterSeq == oldestSeq (3) → nil (BUG-085).
	got = rb.EventsSince(3)
	if got != nil {
		t.Fatalf("expected nil when afterSeq == oldestSeq (3), got %d events", len(got))
	}

	// afterSeq one past oldest → replay seq 5, 6.
	got = rb.EventsSince(4)
	if len(got) != 2 {
		t.Fatalf("expected 2 events after seq 4, got %d", len(got))
	}
	for i, want := range []string{"e5", "e6"} {
		if string(got[i]) != want {
			t.Errorf("event[%d]: expected %q, got %q", i, want, string(got[i]))
		}
	}
}

func TestPush_OverwritesOldest(t *testing.T) {
	const cap = 3
	rb := ws.NewEventRingBuffer(cap)

	rb.Push(1, 0, []byte("a"))
	rb.Push(2, 0, []byte("b"))
	rb.Push(3, 0, []byte("c"))

	if oldest := rb.OldestSeq(); oldest != 1 {
		t.Fatalf("expected oldest seq 1, got %d", oldest)
	}

	// Overwrite seq 1.
	rb.Push(4, 0, []byte("d"))
	if oldest := rb.OldestSeq(); oldest != 2 {
		t.Fatalf("expected oldest seq 2 after overwrite, got %d", oldest)
	}

	// afterSeq == oldestSeq (2) → nil (BUG-085).
	if got := rb.EventsSince(2); got != nil {
		t.Fatalf("expected nil when afterSeq == oldestSeq, got %d events", len(got))
	}

	// afterSeq one past oldest → replay seq 4 only.
	got := rb.EventsSince(3)
	if len(got) != 1 {
		t.Fatalf("expected 1 event, got %d", len(got))
	}
	if string(got[0]) != "d" {
		t.Errorf("expected [d], got [%s]", got[0])
	}
}

// ─── EventsSince ─────────────────────────────────────────────────────────────

func TestEventsSince_EmptyBuffer(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	got := rb.EventsSince(0)
	if got != nil {
		t.Fatalf("expected nil for empty buffer, got %d events", len(got))
	}
}

func TestEventsSince_AfterSpecificSeq(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	for i := uint64(1); i <= 5; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "m%d", i))
	}

	got := rb.EventsSince(3)
	if len(got) != 2 {
		t.Fatalf("expected 2 events after seq 3, got %d", len(got))
	}
	if string(got[0]) != "m4" || string(got[1]) != "m5" {
		t.Errorf("expected [m4, m5], got [%s, %s]", got[0], got[1])
	}
}

func TestEventsSince_TooOld(t *testing.T) {
	const cap = 4
	rb := ws.NewEventRingBuffer(cap)

	for i := uint64(1); i <= 6; i++ {
		rb.Push(i, 0, []byte("x"))
	}

	// Oldest is seq 3. Requesting seq 1 should return nil.
	got := rb.EventsSince(1)
	if got != nil {
		t.Fatalf("expected nil for evicted seq, got %d events", len(got))
	}
}

func TestEventsSince_AtLatestSeq(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	for i := uint64(1); i <= 5; i++ {
		rb.Push(i, 0, []byte("x"))
	}

	got := rb.EventsSince(5)
	// afterSeq equals latest — nothing newer exists.
	if len(got) != 0 {
		t.Fatalf("expected 0 events when afterSeq = latest, got %d", len(got))
	}
}

func TestEventsSince_WraparoundOrder(t *testing.T) {
	const cap = 4
	rb := ws.NewEventRingBuffer(cap)

	// Fill past capacity to force wrap.
	for i := uint64(1); i <= 7; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "v%d", i))
	}

	// afterSeq == oldestSeq (4) → nil (BUG-085).
	if got := rb.EventsSince(4); got != nil {
		t.Fatalf("expected nil when afterSeq == oldestSeq, got %d events", len(got))
	}

	// afterSeq one past oldest → replay seq 6, 7.
	got := rb.EventsSince(5)
	if len(got) != 2 {
		t.Fatalf("expected 2 events, got %d", len(got))
	}
	for i, want := range []string{"v6", "v7"} {
		if string(got[i]) != want {
			t.Errorf("event[%d]: expected %q, got %q", i, want, string(got[i]))
		}
	}
}

func TestEventsSince_AfterSeqZero_ReturnsBehavior(t *testing.T) {
	// afterSeq=0 is below the oldest seq in the buffer (seq starts at 1),
	// so EventsSince treats it as "too old" and returns nil. This is correct:
	// the server can't confirm the buffer covers everything the client missed.
	rb := ws.NewEventRingBuffer(8)
	for i := uint64(1); i <= 3; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "a%d", i))
	}

	got := rb.EventsSince(0)
	if got != nil {
		t.Fatalf("expected nil for afterSeq=0 (before oldest), got %d events", len(got))
	}

	// If we start seqs from 0, afterSeq=0 equals oldest → nil (BUG-085).
	rb2 := ws.NewEventRingBuffer(8)
	rb2.Push(0, 0, []byte("z0"))
	rb2.Push(1, 0, []byte("z1"))
	rb2.Push(2, 0, []byte("z2"))

	got = rb2.EventsSince(0)
	if got != nil {
		t.Fatalf("expected nil when afterSeq == oldestSeq (0), got %d events", len(got))
	}

	// afterSeq one past oldest → replay seq 2 only.
	got = rb2.EventsSince(1)
	if len(got) != 1 {
		t.Fatalf("expected 1 event after seq 1, got %d", len(got))
	}
	if string(got[0]) != "z2" {
		t.Errorf("expected [z2], got [%s]", got[0])
	}
}

func TestEventsSince_AfterSeqEqualsOldest_ReturnsNil(t *testing.T) {
	// BUG-085: When afterSeq == oldestSeq, the client's last event is the
	// oldest in the buffer. We can't guarantee nothing was missed between
	// the evicted event before oldest and oldest itself, so EventsSince
	// must return nil to trigger a full ready payload.
	const cap = 4
	rb := ws.NewEventRingBuffer(cap)

	// Push 6 events: buffer holds seq 3,4,5,6. Oldest = 3.
	for i := uint64(1); i <= 6; i++ {
		rb.Push(i, 0, fmt.Appendf(nil, "e%d", i))
	}

	if oldest := rb.OldestSeq(); oldest != 3 {
		t.Fatalf("expected oldest seq 3, got %d", oldest)
	}

	// afterSeq == oldestSeq (3): must return nil, not empty slice.
	got := rb.EventsSince(3)
	if got == nil {
		// This is the CORRECT behavior after the fix.
		return
	}
	// Before the fix, this returns a non-nil slice [e4, e5, e6].
	// That's wrong because the client at seq 3 might have missed events
	// between the evicted seq 2 and seq 3.
	t.Fatalf("expected nil when afterSeq == oldestSeq, got %d events", len(got))
}

// ─── OldestSeq ───────────────────────────────────────────────────────────────

func TestOldestSeq_Empty(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	if got := rb.OldestSeq(); got != 0 {
		t.Fatalf("expected 0 for empty buffer, got %d", got)
	}
}

func TestOldestSeq_AfterInitialPushes(t *testing.T) {
	rb := ws.NewEventRingBuffer(8)
	rb.Push(10, 0, []byte("x"))
	rb.Push(11, 0, []byte("y"))

	if got := rb.OldestSeq(); got != 10 {
		t.Fatalf("expected oldest seq 10, got %d", got)
	}
}

func TestOldestSeq_AfterWraparound(t *testing.T) {
	const cap = 3
	rb := ws.NewEventRingBuffer(cap)

	rb.Push(10, 0, []byte("a"))
	rb.Push(20, 0, []byte("b"))
	rb.Push(30, 0, []byte("c"))
	rb.Push(40, 0, []byte("d")) // evicts seq 10

	if got := rb.OldestSeq(); got != 20 {
		t.Fatalf("expected oldest seq 20 after wraparound, got %d", got)
	}
}

// ─── Concurrency ─────────────────────────────────────────────────────────────

func TestConcurrent_PushAndEventsSince(t *testing.T) {
	const (
		cap     = 64
		writers = 4
		pushes  = 500
		readers = 4
		reads   = 500
	)
	rb := ws.NewEventRingBuffer(cap)

	var wg sync.WaitGroup

	// Concurrent writers.
	for w := range writers {
		wg.Add(1)
		go func(base uint64) {
			defer wg.Done()
			for i := range uint64(pushes) {
				rb.Push(base+i, 0, []byte("data"))
			}
		}(uint64(w) * pushes)
	}

	// Concurrent readers.
	for range readers {
		wg.Go(func() {
			for range reads {
				_ = rb.EventsSince(0)
				_ = rb.OldestSeq()
			}
		})
	}

	wg.Wait()

	// If we get here without a race detector complaint, the mutex is working.
	// Sanity: buffer should have events.
	if rb.OldestSeq() == 0 {
		t.Fatal("expected non-zero oldest seq after concurrent pushes")
	}
}

// ─── Table-driven: capacity boundary ─────────────────────────────────────────

func TestEventsSince_CapacityBoundaries(t *testing.T) {
	tests := []struct {
		name      string
		cap       int
		pushes    int
		afterSeq  uint64
		wantLen   int // -1 means nil
		wantFirst string
	}{
		{
			name:     "exactly at capacity, afterSeq=0 too old",
			cap:      4,
			pushes:   4,
			afterSeq: 0,
			wantLen:  -1,
		},
		{
			name:     "exactly at capacity, afterSeq == oldest → nil",
			cap:      4,
			pushes:   4,
			afterSeq: 1, // oldest=1, BUG-085: == returns nil
			wantLen:  -1,
		},
		{
			name:      "exactly at capacity, afterSeq one past oldest",
			cap:       4,
			pushes:    4,
			afterSeq:  2,
			wantLen:   2,
			wantFirst: "e3",
		},
		{
			name:     "one past capacity",
			cap:      4,
			pushes:   5,
			afterSeq: 1, // evicted
			wantLen:  -1,
		},
		{
			name:     "one past capacity, afterSeq == oldest → nil",
			cap:      4,
			pushes:   5,
			afterSeq: 2, // oldest=2, BUG-085: == returns nil
			wantLen:  -1,
		},
		{
			name:      "one past capacity, afterSeq one past oldest",
			cap:       4,
			pushes:    5,
			afterSeq:  3,
			wantLen:   2,
			wantFirst: "e4",
		},
		{
			name:     "double capacity, afterSeq == oldest → nil",
			cap:      4,
			pushes:   8,
			afterSeq: 5, // oldest=5, BUG-085: == returns nil
			wantLen:  -1,
		},
		{
			name:      "double capacity, afterSeq one past oldest",
			cap:       4,
			pushes:    8,
			afterSeq:  6,
			wantLen:   2,
			wantFirst: "e7",
		},
		{
			name:     "capacity 1, afterSeq == oldest → nil",
			cap:      1,
			pushes:   3,
			afterSeq: 3, // oldest=3, BUG-085: == returns nil
			wantLen:  -1,
		},
		{
			name:     "capacity 1, afterSeq too old",
			cap:      1,
			pushes:   3,
			afterSeq: 2,
			wantLen:  -1,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			rb := ws.NewEventRingBuffer(tc.cap)
			for i := 1; i <= tc.pushes; i++ {
				rb.Push(uint64(i), 0, fmt.Appendf(nil, "e%d", i))
			}

			got := rb.EventsSince(tc.afterSeq)

			if tc.wantLen == -1 {
				if got != nil {
					t.Fatalf("expected nil, got %d events", len(got))
				}
				return
			}

			if len(got) != tc.wantLen {
				t.Fatalf("expected %d events, got %d", tc.wantLen, len(got))
			}

			if tc.wantLen > 0 && string(got[0]) != tc.wantFirst {
				t.Errorf("first event: expected %q, got %q", tc.wantFirst, string(got[0]))
			}
		})
	}
}
