package codec

import (
	"encoding/json"
	"fmt"
	"math"
)

// MaxBytesKeyNonTerminalSize defines the maximum length of a bytes key encoded
// using the BytesKey KeyCodec.
const MaxBytesKeyNonTerminalSize = math.MaxUint8

func NewBytesKey[T ~[]byte]() NameableKeyCodec[T] { return bytesKey[T]{} }

type bytesKey[T ~[]byte] struct{}

func (b bytesKey[T]) Encode(buffer []byte, key T) (int, error) {
	return copy(buffer, key), nil
}

func (bytesKey[T]) Decode(buffer []byte) (int, T, error) {
	// todo: should we copy it? collections will just discard the buffer, so from coll POV is not needed.
	return len(buffer), buffer, nil
}

func (bytesKey[T]) Size(key T) int {
	return len(key)
}

func (bytesKey[T]) EncodeJSON(value T) ([]byte, error) {
	return json.Marshal(value)
}

func (bytesKey[T]) DecodeJSON(b []byte) (T, error) {
	var t T
	err := json.Unmarshal(b, &t)
	return t, err
}

func (b bytesKey[T]) Stringify(key T) string {
	return fmt.Sprintf("hexBytes:%x", key)
}

func (b bytesKey[T]) KeyType() string {
	return "bytes"
}

func (b bytesKey[T]) EncodeNonTerminal(buffer []byte, key T) (int, error) {
	if len(key) > MaxBytesKeyNonTerminalSize {
		return 0, fmt.Errorf(
			"%w: bytes key non terminal size cannot exceed: %d, got: %d",
			ErrEncoding, MaxBytesKeyNonTerminalSize, len(key),
		)
	}

	buffer[0] = uint8(len(key))
	written := copy(buffer[1:], key)
	return written + 1, nil
}

func (bytesKey[T]) DecodeNonTerminal(buffer []byte) (int, T, error) {
	l := len(buffer)
	if l == 0 {
		return 0, nil, fmt.Errorf("%w: bytes key non terminal decoding cannot have an empty buffer", ErrEncoding)
	}

	keyLength := int(buffer[0])
	if len(buffer[1:]) < keyLength {
		return 0, nil, fmt.Errorf(
			"%w: bytes key non terminal decoding isn't big enough, want at least: %d, got: %d",
			ErrEncoding, keyLength, len(buffer[1:]),
		)
	}
	return 1 + keyLength, buffer[1 : keyLength+1], nil
}

func (bytesKey[T]) SizeNonTerminal(key T) int {
	return len(key) + 1
}

func (b bytesKey[T]) WithName(name string) KeyCodec[T] {
	return NamedKeyCodec[T]{KeyCodec: b, Name: name}
}
