package codec

import (
	"errors"
	"fmt"
)

var ErrEncoding = errors.New("collections: encoding error")

// KeyCodec defines a generic interface which is implemented
// by types that are capable of encoding and decoding collections keys.
type KeyCodec[T any] interface {
	// Encode writes the key bytes into the buffer. Returns the number of
	// bytes written. The implementer must expect the buffer to be at least
	// of length equal to Size(K) for all encodings.
	// It must also return the number of written bytes which must be
	// equal to Size(K) for all encodings not involving varints.
	// In case of encodings involving varints then the returned
	// number of written bytes is allowed to be smaller than Size(K).
	Encode(buffer []byte, key T) (int, error)
	// Decode reads from the provided bytes buffer to decode
	// the key T. Returns the number of bytes read, the type T
	// or an error in case of decoding failure.
	Decode(buffer []byte) (int, T, error)
	// Size returns the buffer size need to encode key T in binary format.
	// The returned value must match what is computed by Encode for all
	// encodings except the ones involving varints. Varints are expected
	// to return the maximum varint bytes buffer length, at the risk of
	// over-estimating in order to pick the most performant path.
	Size(key T) int
	// EncodeJSON encodes the value as JSON.
	EncodeJSON(value T) ([]byte, error)
	// DecodeJSON decodes the provided JSON bytes into an instance of T.
	DecodeJSON(b []byte) (T, error)
	// Stringify returns a string representation of T.
	Stringify(key T) string
	// KeyType returns a string identifier for the type of the key.
	KeyType() string

	// MULTIPART keys

	// EncodeNonTerminal writes the key bytes into the buffer.
	// EncodeNonTerminal is used in multipart keys like Pair
	// when the part of the key being encoded is not the last one,
	// and there needs to be a way to distinguish after how many bytes
	// the first part of the key is finished. The buffer is expected to be
	// at least as big as SizeNonTerminal(key) returns. It returns
	// the amount of bytes written.
	EncodeNonTerminal(buffer []byte, key T) (int, error)
	// DecodeNonTerminal reads the buffer provided and returns
	// the key T. DecodeNonTerminal is used in multipart keys
	// like Pair when the part of the key being decoded is not the
	// last one. It returns the amount of bytes read.
	DecodeNonTerminal(buffer []byte) (int, T, error)
	// SizeNonTerminal returns the maximum size of the key K when used in
	// multipart keys like Pair.
	SizeNonTerminal(key T) int
}

// ValueCodec defines a generic interface which is implemented
// by types that are capable of encoding and decoding collection values.
type ValueCodec[T any] interface {
	// Encode encodes the value T into binary format.
	Encode(value T) ([]byte, error)
	// Decode returns the type T given its binary representation.
	Decode(b []byte) (T, error)
	// EncodeJSON encodes the value as JSON.
	EncodeJSON(value T) ([]byte, error)
	// DecodeJSON decodes the provided JSON bytes into an instance of T.
	DecodeJSON(b []byte) (T, error)
	// Stringify returns a string representation of T.
	Stringify(value T) string
	// ValueType returns the identifier for the type.
	ValueType() string
}

// NewUntypedValueCodec returns an UntypedValueCodec for the provided ValueCodec.
func NewUntypedValueCodec[V any](v ValueCodec[V]) UntypedValueCodec {
	typeName := fmt.Sprintf("%T", *new(V))
	checkType := func(value interface{}) (v V, err error) {
		concrete, ok := value.(V)
		if !ok {
			return v, fmt.Errorf("%w: expected value of type %s, got %T", ErrEncoding, typeName, value)
		}
		return concrete, nil
	}
	return UntypedValueCodec{
		Decode: func(b []byte) (interface{}, error) { return v.Decode(b) },
		Encode: func(value interface{}) ([]byte, error) {
			concrete, err := checkType(value)
			if err != nil {
				return nil, err
			}
			return v.Encode(concrete)
		},
		DecodeJSON: func(b []byte) (interface{}, error) {
			return v.DecodeJSON(b)
		},
		EncodeJSON: func(value interface{}) ([]byte, error) {
			concrete, err := checkType(value)
			if err != nil {
				return nil, err
			}
			return v.EncodeJSON(concrete)
		},
		Stringify: func(value interface{}) (string, error) {
			concrete, err := checkType(value)
			if err != nil {
				return "", err
			}
			return v.Stringify(concrete), nil
		},
		ValueType: func() string { return v.ValueType() },
	}
}

// UntypedValueCodec wraps a ValueCodec to expose an untyped API for encoding and decoding values.
type UntypedValueCodec struct {
	Decode     func(b []byte) (interface{}, error)
	Encode     func(value interface{}) ([]byte, error)
	DecodeJSON func(b []byte) (interface{}, error)
	EncodeJSON func(value interface{}) ([]byte, error)
	Stringify  func(value interface{}) (string, error)
	ValueType  func() string
}

// KeyToValueCodec converts a KeyCodec into a ValueCodec.
func KeyToValueCodec[K any](keyCodec KeyCodec[K]) NameableValueCodec[K] {
	return keyToValueCodec[K]{kc: keyCodec}
}

// keyToValueCodec is a ValueCodec that wraps a KeyCodec to make it behave like a ValueCodec.
type keyToValueCodec[K any] struct {
	kc KeyCodec[K]
}

func (k keyToValueCodec[K]) EncodeJSON(value K) ([]byte, error) {
	return k.kc.EncodeJSON(value)
}

func (k keyToValueCodec[K]) DecodeJSON(b []byte) (K, error) {
	return k.kc.DecodeJSON(b)
}

func (k keyToValueCodec[K]) Encode(value K) ([]byte, error) {
	buf := make([]byte, k.kc.Size(value))
	_, err := k.kc.Encode(buf, value)
	return buf, err
}

func (k keyToValueCodec[K]) Decode(b []byte) (K, error) {
	r, key, err := k.kc.Decode(b)
	if err != nil {
		var key K
		return key, err
	}

	if r != len(b) {
		var key K
		return key, fmt.Errorf("%w: was supposed to fully consume the key '%x', consumed %d out of %d", ErrEncoding, b, r, len(b))
	}
	return key, nil
}

func (k keyToValueCodec[K]) Stringify(value K) string {
	return k.kc.Stringify(value)
}

func (k keyToValueCodec[K]) ValueType() string {
	return k.kc.KeyType()
}

func (k keyToValueCodec[K]) WithName(name string) ValueCodec[K] {
	return NamedValueCodec[K]{ValueCodec: k, Name: name}
}
