package wal

import (
	"bytes"
	"encoding/binary"
	"errors"
	"fmt"
)

// WalEntry represents a single WAL record.
type WalEntry struct {
	OpType    uint8
	ValueKind uint8
	Key       []byte
	Value     []byte
}

// Validate checks that the entry fields are consistent with the design rules.
func (e *WalEntry) Validate() error {
	keyLen := len(e.Key)
	if keyLen == 0 || keyLen > int(MaxWalKeyBytes) {
		return fmt.Errorf("wal: invalid key length %d", keyLen)
	}

	valLen := len(e.Value)

	switch e.OpType {
	case OpPut:
		switch e.ValueKind {
		case VKInline:
			if valLen > int(MaxWalInlineValueBytes) {
				return fmt.Errorf("wal: inline value length %d out of range [0, %d]", valLen, MaxWalInlineValueBytes)
			}
		case VKValueLogPointer:
			if valLen == 0 {
				return errors.New("wal: value log pointer requires non-empty value")
			}
		default:
			return fmt.Errorf("wal: put requires valueKind Inline(1) or ValueLogPointer(2), got %d", e.ValueKind)
		}

	case OpDelete:
		if e.ValueKind != VKNone {
			return fmt.Errorf("wal: delete requires valueKind None(0), got %d", e.ValueKind)
		}
		if valLen != 0 {
			return fmt.Errorf("wal: delete requires empty value, got length %d", valLen)
		}

	default:
		return fmt.Errorf("wal: invalid opType %d", e.OpType)
	}

	return nil
}

// EncodeEntry serializes a WalEntry into a byte slice.
func EncodeEntry(e *WalEntry) ([]byte, error) {
	if err := e.Validate(); err != nil {
		return nil, err
	}

	keyLen := uint64(len(e.Key))
	valLen := uint64(len(e.Value))

	// Size: 1 (opType) + 1 (valueKind) + varint(keyLen) + varint(valLen) + key + value
	size := 2 + MaxWalVarintBytes + MaxWalVarintBytes + len(e.Key) + len(e.Value)
	buf := make([]byte, size)

	buf[0] = e.OpType
	buf[1] = e.ValueKind
	n := 2
	n += binary.PutUvarint(buf[n:], keyLen)
	n += binary.PutUvarint(buf[n:], valLen)
	n += copy(buf[n:], e.Key)
	n += copy(buf[n:], e.Value)

	return buf[:n], nil
}

// DecodeEntry deserializes a WalEntry from a byte slice.
// Returns the decoded entry and the number of bytes consumed.
func DecodeEntry(data []byte) (entry *WalEntry, consumed int, err error) {
	if len(data) < 2 {
		return nil, 0, errors.New("wal: data too short for entry header")
	}

	opType := data[0]
	valueKind := data[1]
	r := bytes.NewReader(data[2:])

	keyLen, err := binary.ReadUvarint(r)
	if err != nil {
		return nil, 0, fmt.Errorf("wal: reading key length: %w", err)
	}
	valLen, err := binary.ReadUvarint(r)
	if err != nil {
		return nil, 0, fmt.Errorf("wal: reading value length: %w", err)
	}

	// Calculate consumed so far: 2 header bytes + bytes read from reader
	consumed = 2 + (len(data) - 2 - r.Len())

	// Read key
	remaining := len(data) - consumed
	if uint64(remaining) < keyLen {
		return nil, 0, fmt.Errorf("wal: data truncated: need %d bytes for key, have %d", keyLen, remaining)
	}
	key := make([]byte, keyLen)
	copy(key, data[consumed:consumed+int(keyLen)])
	consumed += int(keyLen)

	// Read value
	remaining = len(data) - consumed
	if uint64(remaining) < valLen {
		return nil, 0, fmt.Errorf("wal: data truncated: need %d bytes for value, have %d", valLen, remaining)
	}
	value := make([]byte, valLen)
	copy(value, data[consumed:consumed+int(valLen)])
	consumed += int(valLen)

	e := &WalEntry{
		OpType:    opType,
		ValueKind: valueKind,
		Key:       key,
		Value:     value,
	}

	if err := e.Validate(); err != nil {
		return nil, 0, err
	}

	return e, consumed, nil
}
