package memtable

import (
	"sync"
	"testing"
)

func TestMemTablePendingInvisible(t *testing.T) {
	mt := NewMemTable(4096)

	// Put a pending entry — must not be visible.
	if err := mt.PutPending([]byte("key1"), []byte("val1"), 1); err != nil {
		t.Fatalf("PutPending: %v", err)
	}

	res := mt.Get([]byte("key1"))
	if res.Found {
		t.Fatal("pending entry should not be visible")
	}

	// Publish sequence 1 — entry becomes visible.
	mt.Publish(1)

	res = mt.Get([]byte("key1"))
	if !res.Found {
		t.Fatal("published entry should be visible")
	}
	if string(res.Value) != "val1" {
		t.Fatalf("value mismatch: got %q, want %q", res.Value, "val1")
	}
	if res.Sequence != 1 {
		t.Fatalf("sequence mismatch: got %d, want %d", res.Sequence, 1)
	}
}

func TestMemTableAbortedInvisible(t *testing.T) {
	mt := NewMemTable(4096)

	if err := mt.PutPending([]byte("key1"), []byte("val1"), 1); err != nil {
		t.Fatalf("PutPending: %v", err)
	}

	// Abort sequence 1 before publishing.
	mt.Abort(1)

	// Publish up to sequence 10 — aborted entry should stay invisible.
	mt.Publish(10)

	res := mt.Get([]byte("key1"))
	if res.Found {
		t.Fatal("aborted entry should not be visible")
	}
}

func TestMemTableMultiplePublish(t *testing.T) {
	mt := NewMemTable(8192)

	if err := mt.PutPending([]byte("a"), []byte("va"), 1); err != nil {
		t.Fatalf("PutPending a: %v", err)
	}
	if err := mt.PutPending([]byte("b"), []byte("vb"), 2); err != nil {
		t.Fatalf("PutPending b: %v", err)
	}

	// Neither visible before publish.
	if mt.Get([]byte("a")).Found {
		t.Fatal("a should not be visible before publish")
	}
	if mt.Get([]byte("b")).Found {
		t.Fatal("b should not be visible before publish")
	}

	// Publish both.
	mt.Publish(2)

	if !mt.Get([]byte("a")).Found {
		t.Fatal("a should be visible after publish")
	}
	if !mt.Get([]byte("b")).Found {
		t.Fatal("b should be visible after publish")
	}

	if string(mt.Get([]byte("a")).Value) != "va" {
		t.Fatal("value mismatch for a")
	}
	if string(mt.Get([]byte("b")).Value) != "vb" {
		t.Fatal("value mismatch for b")
	}
}

func TestMemTableReserve(t *testing.T) {
	mt := NewMemTable(4096)

	// Reserve 10 small entries — should succeed.
	entries := make([]ReserveEntry, 10)
	for i := range entries {
		entries[i] = ReserveEntry{
			Key:   []byte("k"),
			Value: []byte("v"),
		}
	}
	total, err := mt.Reserve(entries)
	if err != nil {
		t.Fatalf("Reserve 10 entries: %v", err)
	}
	if total == 0 {
		t.Fatal("expected non-zero total size")
	}

	// Reserve more than remaining — should fail.
	big := []ReserveEntry{{
		Key:   make([]byte, 2048),
		Value: make([]byte, 2048),
	}}
	_, err = mt.Reserve(big)
	if err == nil {
		t.Fatal("expected ErrMemTableFull for oversized reserve")
	}
}

func TestMemTableConcurrentReads(t *testing.T) {
	mt := NewMemTable(8192)

	// Write 10 pending entries.
	for i := 0; i < 10; i++ {
		key := []byte{byte('a' + i)}
		val := []byte{byte(i)}
		if err := mt.PutPending(key, val, uint64(i+1)); err != nil {
			t.Fatalf("PutPending %d: %v", i, err)
		}
	}

	// Publish all.
	mt.Publish(10)

	// Concurrent reads should all see published values.
	var wg sync.WaitGroup
	for g := 0; g < 4; g++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for i := 0; i < 100; i++ {
				key := []byte{byte('a' + (i % 10))}
				res := mt.Get(key)
				if !res.Found {
					t.Errorf("key %s not found", key)
					return
				}
			}
		}()
	}
	wg.Wait()
}

func TestMemTableIteratorSkipsPending(t *testing.T) {
	mt := NewMemTable(4096)

	_ = mt.PutPending([]byte("pending"), []byte("p"), 1)
	_ = mt.PutPending([]byte("published"), []byte("pub"), 2)
	mt.Publish(2) // publishes "published" at seq 2

	// Abort seq 1 — stays pending.
	// Actually we already published up to 2 which would publish seq 1 too.
	// Let's test differently: insert a pending entry after publish.
	_ = mt.PutPending([]byte("still_pending"), []byte("sp"), 3)

	it := mt.NewIterator()
	count := 0
	for it.Valid() {
		count++
		it.Next()
	}

	// Should see "pending" (seq 1, now published) and "published" (seq 2),
	// but not "still_pending" (seq 3, still pending).
	if count != 2 {
		t.Fatalf("expected 2 visible entries, got %d", count)
	}
}

func TestMemTableDeletePending(t *testing.T) {
	mt := NewMemTable(4096)

	// Insert then delete.
	_ = mt.PutPending([]byte("key1"), []byte("val1"), 1)
	_ = mt.DeletePending([]byte("key1"), 2)

	mt.Publish(2)

	// After publish, the latest entry is a tombstone (nil value).
	res := mt.Get([]byte("key1"))
	if !res.Found {
		t.Fatal("tombstone entry should still be 'found'")
	}
	if res.Value != nil {
		t.Fatalf("expected nil value for tombstone, got %q", res.Value)
	}
}

func TestMemTableApproximateSize(t *testing.T) {
	mt := NewMemTable(4096)

	if mt.ApproximateSize() != 0 {
		t.Fatal("new memtable should have zero approximate size")
	}
	if mt.UsableCapacity() != 4096 {
		t.Fatalf("usable capacity: got %d, want 4096", mt.UsableCapacity())
	}
}
