package memtable

import (
	"sync"
	"testing"

	"github.com/stretchr/testify/require"
)

func TestArenaAllocate(t *testing.T) {
	a := NewArena(1024)

	off, err := a.Allocate(9)
	require.NoError(t, err)
	require.Equal(t, uint32(0), off)

	copy(a.GetBytes(off, 9), "test data")
	require.Equal(t, []byte("test data"), a.GetBytes(off, 9))

	off2, err := a.Allocate(200)
	require.NoError(t, err)
	// 9 bytes aligned to 16, so second allocation starts at 16
	require.Equal(t, uint32(16), off2)
}

func TestArenaFull(t *testing.T) {
	a := NewArena(64)

	off, err := a.Allocate(60)
	require.NoError(t, err)
	require.Equal(t, uint32(0), off) // 60 aligned to 64

	_, err = a.Allocate(10)
	require.ErrorIs(t, err, ErrArenaFull)
}

func TestArenaAlignment(t *testing.T) {
	a := NewArena(1024)

	sizes := []uint32{1, 3, 5, 7, 8, 9, 13, 16, 100}
	for _, s := range sizes {
		off, err := a.Allocate(s)
		require.NoError(t, err, "size=%d", s)
		require.Equal(t, uint32(0), off%8, "offset %d not 8-byte aligned for size %d", off, s)
	}
}

func TestArenaConcurrent(t *testing.T) {
	a := NewArena(65536)

	const goroutines = 64
	const perGoroutine = 32

	var wg sync.WaitGroup
	wg.Add(goroutines)

	offsets := make([][]uint32, goroutines)

	for i := 0; i < goroutines; i++ {
		i := i
		go func() {
			defer wg.Done()
			locals := make([]uint32, 0, perGoroutine)
			for j := 0; j < perGoroutine; j++ {
				off, err := a.Allocate(8)
				if err == nil {
					// Write unique data to detect corruption
					copy(a.GetBytes(off, 8), []byte{byte(i), byte(j), 0, 0, 0, 0, 0, 0})
					locals = append(locals, off)
				}
			}
			offsets[i] = locals
		}()
	}

	wg.Wait()

	// Verify data integrity — each byte pair must match its goroutine/j index
	for i, locals := range offsets {
		for j, off := range locals {
			data := a.GetBytes(off, 8)
			require.Equal(t, byte(i), data[0], "goroutine %d offset %d", i, off)
			require.Equal(t, byte(j), data[1], "iteration %d offset %d", j, off)
		}
	}
}

func TestArenaReserve(t *testing.T) {
	a := NewArena(128)

	require.NoError(t, a.Reserve(64))
	require.NoError(t, a.Reserve(128))
	require.ErrorIs(t, a.Reserve(129), ErrArenaFull)

	// Allocate some space
	_, err := a.Allocate(32)
	require.NoError(t, err) // 32 aligned = 32

	// 128 - 32 = 96 remaining
	require.NoError(t, a.Reserve(96))
	require.ErrorIs(t, a.Reserve(97), ErrArenaFull)
}

func TestArenaRemainingAndCapacity(t *testing.T) {
	cap := uint32(256)
	a := NewArena(cap)

	require.Equal(t, cap, a.Capacity())
	require.Equal(t, cap, a.Remaining())

	_, err := a.Allocate(10)
	require.NoError(t, err) // 10 aligned to 16

	require.Equal(t, cap-16, a.Remaining())
}
