package pdu

import (
	"sync"
	"testing"
	"time"
)

func TestRetrieveBufferOfRequestedSize(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024
	buffer := bpm.Get(size)

	if buffer == nil {
		t.Fatalf("Expected buffer, got nil")
	}

	if buffer.Cap() != size {
		t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
	}
}

func TestRequestBufferSizeZero(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 0
	buffer := bpm.Get(size)

	if buffer == nil {
		t.Fatalf("Expected buffer, got nil")
	}

	if buffer.Cap() != size {
		t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
	}
}

func TestConcurrentAccessToBufferPool(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024
	var wg sync.WaitGroup
	numRoutines := 100

	for i := 0; i < numRoutines; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			buffer := bpm.Get(size)
			if buffer == nil {
				t.Errorf("Expected buffer, got nil")
			}
			if buffer.Cap() != size {
				t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
			}
		}()
	}

	wg.Wait()
}

func TestGetBufferLockUnlock(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024
	buffer := bpm.Get(size)

	if buffer == nil {
		t.Fatalf("Expected buffer, got nil")
	}

	if buffer.Cap() != size {
		t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
	}
}

func TestVerifyPoolCreationForNewSizes(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 512
	buffer := bpm.Get(size)

	if buffer == nil {
		t.Fatalf("Expected buffer, got nil")
	}

	if buffer.Cap() != size {
		t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
	}
}

func TestBufferPoolManagerGetBuffer(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024
	buffer := bpm.Get(size)

	if buffer == nil {
		t.Fatalf("Expected buffer, got nil")
	}

	if buffer.Cap() != size {
		t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
	}
}

func TestGetBufferWithMultipleSizes(t *testing.T) {
	bpm := NewBufferPoolManager()

	sizes := []int{512, 1024, 2048}
	for _, size := range sizes {
		buffer := bpm.Get(size)

		if buffer == nil {
			t.Fatalf("Expected buffer for size %d, got nil", size)
		}

		if buffer.Cap() != size {
			t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
		}
	}
}

func TestGetBufferIsAlwaysZero(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024 * 64
	for i := 0; i < 1000; i++ {
		buffer := bpm.Get(size)

		if buffer == nil {
			t.Fatalf("Expected buffer for size %d, got nil", size)
		}

		if buffer.Cap() != size {
			t.Errorf("Expected buffer size %d, got %d", size, buffer.Cap())
		}

		for _, b := range buffer.Bytes() {
			if b != 0 {
				t.Errorf("Expected buffer to be zero, got %d", b)
			}
		}

		bpm.Put(buffer)
	}
}

func TestPoolReusesBuffers(t *testing.T) {
	bpm := NewBufferPoolManager()

	size := 1024
	buffer := bpm.Get(size)
	initialPtr := buffer
	bpm.Put(buffer)
	for i := 0; i < 1000; i++ {
		buffer = bpm.Get(size)
		if buffer != initialPtr {
			t.Errorf("Expected initial buffer (%p) to be reused, got %p", initialPtr, buffer)
		}
		bpm.Put(buffer)
	}
}

// region benchmark
func BenchmarkBufferPoolManager(b *testing.B) {
	bpm := NewBufferPoolManager()
	bufSize := 128 * 1024 // a PDU should not be larger than this... Even this is way too large

	b.ResetTimer()

	for i := 0; i < b.N; i++ {
		// Benchmark Get
		buf := bpm.Get(bufSize)
		// Benchmark Put
		bpm.Put(buf)
	}
}

func BenchmarkBufferPoolManager_Concurrent(b *testing.B) {
	bpm := NewBufferPoolManager()
	bufSize := 128 * 1024 // a PDU should not be larger than this... Even this is way too large

	b.ResetTimer()

	var wg sync.WaitGroup
	concurrency := 1000

	for i := 0; i < concurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for j := 0; j < b.N/concurrency; j++ {
				buf := bpm.Get(bufSize)
				bpm.Put(buf)
			}
		}()
	}

	wg.Wait()
}

func BenchmarkBufferPoolManager_Memory(b *testing.B) {
	bpm := NewBufferPoolManager()
	bufSize := 128 * 1024 // a PDU should not be larger than this... Even this is way too large
	b.ResetTimer()

	var i byte
	buf := bpm.Get(bufSize)
	b.StopTimer()

	// Simulate some work
	time.Sleep(10 * time.Millisecond)
	for range buf.Bytes() {
		buf.WriteByte(i % 255)
	}

	b.StartTimer()
	for i := 0; i < b.N; i++ {
		bpm.Put(buf)
		buf = bpm.Get(bufSize)
	}
}
