package memtable

import (
	"errors"
	"fmt"
	"sync"
	"sync/atomic"
)

// ErrMemTableFull is returned when the memtable cannot reserve enough space.
var ErrMemTableFull = errors.New("memtable: insufficient capacity")

// ReserveEntry describes a single key-value pair to be reserved.
type ReserveEntry struct {
	Key      []byte
	Value    []byte
	IsDelete bool
}

// GetResult is returned by MemTable.Get.
type GetResult struct {
	Found    bool
	Value    []byte
	Sequence uint64
}

// MemTable wraps an Arena and SkipList with publish/abort semantics.
//
// Writers first reserve capacity, then insert pending entries, then either
// publish (making them visible) or abort (leaving them invisible forever).
// Readers only see published entries that have not been aborted.
type MemTable struct {
	arena     *Arena
	skiplist  *SkipList
	published atomic.Uint64 // highest published sequence
	aborted   sync.Map      // map[uint64]struct{} — set of aborted sequences
}

// NewMemTable creates a MemTable backed by an arena of the given capacity.
func NewMemTable(capacity uint32) *MemTable {
	arena := NewArena(capacity)
	return &MemTable{
		arena:    arena,
		skiplist: NewSkipList(arena),
	}
}

// Reserve estimates whether the arena has enough space for all entries.
// It returns the total estimated size for caller tracking.
//
// The estimate is conservative: key + value bytes plus a fixed metadata
// overhead per entry. Since Phase 1 stores nodes on the Go heap (not in
// the arena), this reservation is primarily a capacity gate for future
// arena-backed phases.
func (mt *MemTable) Reserve(entries []ReserveEntry) (uint32, error) {
	const metadataOverhead uint32 = 32 // per-entry overhead estimate

	var totalSize uint32
	for _, e := range entries {
		sz := metadataOverhead + uint32(len(e.Key)) + uint32(len(e.Value))
		// Align to 8 bytes
		sz = (sz + 7) &^ uint32(7)
		totalSize += sz
	}

	if err := mt.arena.Reserve(totalSize); err != nil {
		return 0, ErrMemTableFull
	}
	return totalSize, nil
}

// PutPending inserts a key-value pair as pending (invisible to Get/Iterator).
func (mt *MemTable) PutPending(key []byte, value []byte, sequence uint64) error {
	return mt.skiplist.Put(key, value, sequence, true)
}

// DeletePending inserts a tombstone entry (nil value) as pending.
func (mt *MemTable) DeletePending(key []byte, sequence uint64) error {
	return mt.skiplist.Put(key, nil, sequence, true)
}

// Publish makes all pending entries with sequence <= upToSequence visible.
//
// It iterates the skip list and re-inserts each pending entry with
// pending=false, which the lock-free reader path will then observe.
// Aborted entries are skipped and remain invisible forever.
func (mt *MemTable) Publish(upToSequence uint64) {
	// Collect entries to publish under the iterator (which skips pending).
	// We need a raw walk, so we access the skiplist directly.
	type entry struct {
		key      []byte
		value    []byte
		sequence uint64
	}

	// Walk the raw skip list level-0 chain to find pending entries.
	// We cannot use NewIterator because it skips pending entries.
	var toPublish []entry

	mt.skiplist.mu.Lock()
	node := mt.skiplist.head.next[0].Load()
	for node != nil {
		if node.pending && node.sequence <= upToSequence {
			if _, aborted := mt.aborted.Load(node.sequence); !aborted {
				toPublish = append(toPublish, entry{
					key:      node.key,
					value:    node.value,
					sequence: node.sequence,
				})
			}
		}
		node = node.next[0].Load()
	}
	mt.skiplist.mu.Unlock()

	// Re-put each entry as published. Each call acquires the skiplist mutex.
	for _, e := range toPublish {
		if err := mt.skiplist.Put(e.key, e.value, e.sequence, false); err != nil {
			// Reserve guaranteed space, so this should never happen.
			// If it does, it's an internal invariant violation.
			panic(fmt.Sprintf("memtable: Put failed after Reserve: %v", err))
		}
	}

	// Update high-water mark after all entries are visible.
	mt.published.Store(upToSequence)
}

// Abort marks a sequence as aborted. Aborted entries are never visible to readers.
func (mt *MemTable) Abort(sequence uint64) {
	mt.aborted.Store(sequence, struct{}{})
}

// Get retrieves the latest visible value for key.
//
// A value is visible if it is published (pending=false in the skip list)
// and not in the aborted set.
func (mt *MemTable) Get(key []byte) *GetResult {
	found, value, sequence := mt.skiplist.Get(key)
	if !found {
		return &GetResult{Found: false}
	}
	return &GetResult{
		Found:    true,
		Value:    value,
		Sequence: sequence,
	}
}

// NewIterator returns a forward iterator over all published (visible) entries.
// Pending and aborted entries are automatically skipped.
func (mt *MemTable) NewIterator() *Iterator {
	return mt.skiplist.NewIterator()
}

// ApproximateSize returns the number of bytes used in the arena.
func (mt *MemTable) ApproximateSize() uint64 {
	return uint64(mt.arena.Capacity() - mt.arena.Remaining())
}

// UsableCapacity returns the remaining bytes available in the arena.
func (mt *MemTable) UsableCapacity() uint32 {
	return mt.arena.Remaining()
}
