package buffer

import (
	"sync"
	"testing"
)

// TestNewRingBuffer verifies constructor returns buffer with correct capacity.
func TestNewRingBuffer(t *testing.T) {
	rb := NewRingBuffer[string](5)
	if rb == nil {
		t.Fatal("NewRingBuffer returned nil")
	}
	if rb.Len() != 0 {
		t.Errorf("expected Len() = 0, got %d", rb.Len())
	}
}

// TestPushAndSnapshot verifies basic push and snapshot operations.
// Push 3 items to a capacity-5 buffer, snapshot should return all 3 in order.
func TestPushAndSnapshot(t *testing.T) {
	rb := NewRingBuffer[string](5)
	rb.Push("a")
	rb.Push("b")
	rb.Push("c")

	snapshot := rb.Snapshot()
	if len(snapshot) != 3 {
		t.Fatalf("expected 3 items, got %d", len(snapshot))
	}

	expected := []string{"a", "b", "c"}
	for i, v := range expected {
		if snapshot[i] != v {
			t.Errorf("snapshot[%d] = %q, expected %q", i, snapshot[i], v)
		}
	}
}

// TestOverflowDropsOldest verifies that when buffer is full, oldest items are dropped.
// Push 6 items to capacity-5 buffer, snapshot should return last 5.
func TestOverflowDropsOldest(t *testing.T) {
	rb := NewRingBuffer[string](5)
	items := []string{"a", "b", "c", "d", "e", "f"}
	for _, item := range items {
		rb.Push(item)
	}

	snapshot := rb.Snapshot()
	if len(snapshot) != 5 {
		t.Fatalf("expected 5 items, got %d", len(snapshot))
	}

	// Should contain b, c, d, e, f (oldest "a" was dropped)
	expected := []string{"b", "c", "d", "e", "f"}
	for i, v := range expected {
		if snapshot[i] != v {
			t.Errorf("snapshot[%d] = %q, expected %q", i, snapshot[i], v)
		}
	}
}

// TestDroppedCount verifies that dropped items are tracked correctly.
// Push 10 items to capacity-5 buffer, DroppedCount should return 5.
func TestDroppedCount(t *testing.T) {
	rb := NewRingBuffer[int](5)
	for i := 0; i < 10; i++ {
		rb.Push(i)
	}

	dropped := rb.DroppedCount()
	if dropped != 5 {
		t.Errorf("expected DroppedCount() = 5, got %d", dropped)
	}
}

// TestLen verifies Len() returns current count (0 to capacity).
func TestLen(t *testing.T) {
	rb := NewRingBuffer[string](5)

	// Initially empty
	if rb.Len() != 0 {
		t.Errorf("expected Len() = 0, got %d", rb.Len())
	}

	// After 3 pushes
	rb.Push("a")
	rb.Push("b")
	rb.Push("c")
	if rb.Len() != 3 {
		t.Errorf("expected Len() = 3, got %d", rb.Len())
	}

	// After reaching capacity
	rb.Push("d")
	rb.Push("e")
	if rb.Len() != 5 {
		t.Errorf("expected Len() = 5, got %d", rb.Len())
	}

	// After overflow (should stay at capacity)
	rb.Push("f")
	rb.Push("g")
	if rb.Len() != 5 {
		t.Errorf("expected Len() = 5 after overflow, got %d", rb.Len())
	}
}

// TestClear verifies Clear() resets buffer but preserves dropped count.
func TestClear(t *testing.T) {
	rb := NewRingBuffer[int](5)

	// Push 7 items (2 dropped)
	for i := 0; i < 7; i++ {
		rb.Push(i)
	}

	if rb.Len() != 5 {
		t.Errorf("before clear: expected Len() = 5, got %d", rb.Len())
	}
	if rb.DroppedCount() != 2 {
		t.Errorf("before clear: expected DroppedCount() = 2, got %d", rb.DroppedCount())
	}

	rb.Clear()

	if rb.Len() != 0 {
		t.Errorf("after clear: expected Len() = 0, got %d", rb.Len())
	}
	if rb.DroppedCount() != 2 {
		t.Errorf("after clear: expected DroppedCount() = 2 (preserved), got %d", rb.DroppedCount())
	}

	snapshot := rb.Snapshot()
	if len(snapshot) != 0 {
		t.Errorf("after clear: expected empty snapshot, got %d items", len(snapshot))
	}
}

// TestFIFOOrder verifies items are returned in FIFO order after overflow.
// Push items 1-10 to capacity-5 buffer, snapshot should return 6,7,8,9,10 in order.
func TestFIFOOrder(t *testing.T) {
	rb := NewRingBuffer[int](5)
	for i := 1; i <= 10; i++ {
		rb.Push(i)
	}

	snapshot := rb.Snapshot()
	if len(snapshot) != 5 {
		t.Fatalf("expected 5 items, got %d", len(snapshot))
	}

	expected := []int{6, 7, 8, 9, 10}
	for i, v := range expected {
		if snapshot[i] != v {
			t.Errorf("snapshot[%d] = %d, expected %d", i, snapshot[i], v)
		}
	}
}

// TestConcurrentPush verifies thread safety - concurrent pushes should not panic or race.
func TestConcurrentPush(t *testing.T) {
	rb := NewRingBuffer[int](100)
	const goroutines = 10
	const pushesPerGoroutine = 100

	var wg sync.WaitGroup
	wg.Add(goroutines)

	for g := 0; g < goroutines; g++ {
		go func(id int) {
			defer wg.Done()
			for i := 0; i < pushesPerGoroutine; i++ {
				rb.Push(id*pushesPerGoroutine + i)
			}
		}(g)
	}

	wg.Wait()

	// Buffer should be at capacity
	if rb.Len() != 100 {
		t.Errorf("expected Len() = 100, got %d", rb.Len())
	}

	// Total pushed: 10 * 100 = 1000, capacity 100, so 900 dropped
	totalPushed := goroutines * pushesPerGoroutine
	expectedDropped := int64(totalPushed - 100)
	if rb.DroppedCount() != expectedDropped {
		t.Errorf("expected DroppedCount() = %d, got %d", expectedDropped, rb.DroppedCount())
	}

	// Snapshot should work without panic and return 100 items
	snapshot := rb.Snapshot()
	if len(snapshot) != 100 {
		t.Errorf("expected 100 items in snapshot, got %d", len(snapshot))
	}
}

// TestEmptyBuffer verifies empty buffer behavior.
func TestEmptyBuffer(t *testing.T) {
	rb := NewRingBuffer[string](5)

	if rb.Len() != 0 {
		t.Errorf("expected Len() = 0, got %d", rb.Len())
	}

	if rb.DroppedCount() != 0 {
		t.Errorf("expected DroppedCount() = 0, got %d", rb.DroppedCount())
	}

	snapshot := rb.Snapshot()
	if snapshot == nil {
		t.Error("expected non-nil snapshot for empty buffer")
	}
	if len(snapshot) != 0 {
		t.Errorf("expected empty snapshot, got %d items", len(snapshot))
	}
}

// TestSingleItemBuffer verifies buffer with capacity 1 works correctly.
func TestSingleItemBuffer(t *testing.T) {
	rb := NewRingBuffer[string](1)

	rb.Push("first")
	if rb.Len() != 1 {
		t.Errorf("expected Len() = 1, got %d", rb.Len())
	}

	snapshot := rb.Snapshot()
	if len(snapshot) != 1 || snapshot[0] != "first" {
		t.Errorf("expected [first], got %v", snapshot)
	}

	rb.Push("second")
	if rb.Len() != 1 {
		t.Errorf("expected Len() = 1 after overflow, got %d", rb.Len())
	}
	if rb.DroppedCount() != 1 {
		t.Errorf("expected DroppedCount() = 1, got %d", rb.DroppedCount())
	}

	snapshot = rb.Snapshot()
	if len(snapshot) != 1 || snapshot[0] != "second" {
		t.Errorf("expected [second], got %v", snapshot)
	}
}
