package collections

import (
	"bytes"
	"context"
	"fmt"

	"cosmossdk.io/collections/codec"
)

// WithKeySetUncheckedValue changes the behavior of the KeySet when it encounters
// a value different from '[]byte{}', by default the KeySet errors when this happens.
// This option allows to ignore the value and continue with the operation, in turn
// the value will be cleared out and set to '[]byte{}'.
// You should never use this option if you're creating a new state object from scratch.
// This should be used only to behave nicely in case you have used values different
// from '[]byte{}' in your storage before migrating to collections.
func WithKeySetUncheckedValue() func(opt *keySetOptions) {
	return func(opt *keySetOptions) {
		opt.uncheckedValue = true
	}
}

// WithKeySetSecondaryIndex changes the behavior of the KeySet to be a secondary index.
func WithKeySetSecondaryIndex() func(opt *keySetOptions) {
	return func(opt *keySetOptions) {
		opt.isSecondaryIndex = true
	}
}

type keySetOptions struct {
	uncheckedValue   bool
	isSecondaryIndex bool
}

// KeySet builds on top of a Map and represents a collection retaining only a set
// of keys and no value. It can be used, for example, in an allow list.
type KeySet[K any] Map[K, NoValue]

// NewKeySet returns a KeySet given a Schema, Prefix a human name for the collection
// and a KeyCodec for the key K.
func NewKeySet[K any](
	schema *SchemaBuilder,
	prefix Prefix,
	name string,
	keyCodec codec.KeyCodec[K],
	options ...func(opt *keySetOptions),
) KeySet[K] {
	o := new(keySetOptions)
	for _, opt := range options {
		opt(o)
	}
	vc := noValueCodec
	if o.uncheckedValue {
		vc = codec.NewAltValueCodec(vc, func(_ []byte) (NoValue, error) { return NoValue{}, nil })
	}
	return (KeySet[K])(NewMap(schema, prefix, name, keyCodec, vc, withMapSecondaryIndex(o.isSecondaryIndex)))
}

// Set adds the key to the KeySet. Errors on encoding problems.
func (k KeySet[K]) Set(ctx context.Context, key K) error {
	return (Map[K, NoValue])(k).Set(ctx, key, NoValue{})
}

// Has returns if the key is present in the KeySet.
// An error is returned only in case of encoding problems.
func (k KeySet[K]) Has(ctx context.Context, key K) (bool, error) {
	return (Map[K, NoValue])(k).Has(ctx, key)
}

// Remove removes the key for the KeySet. An error is returned in case of
// encoding error, it won't report through the error if the key was
// removed or not.
func (k KeySet[K]) Remove(ctx context.Context, key K) error {
	return (Map[K, NoValue])(k).Remove(ctx, key)
}

// Iterate iterates over the keys given the provided Ranger. If ranger is nil,
// the KeySetIterator will include all the existing keys within the KeySet.
func (k KeySet[K]) Iterate(ctx context.Context, ranger Ranger[K]) (KeySetIterator[K], error) {
	iter, err := (Map[K, NoValue])(k).Iterate(ctx, ranger)
	if err != nil {
		return KeySetIterator[K]{}, err
	}

	return (KeySetIterator[K])(iter), nil
}

func (k KeySet[K]) IterateRaw(ctx context.Context, start, end []byte, order Order) (Iterator[K, NoValue], error) {
	return (Map[K, NoValue])(k).IterateRaw(ctx, start, end, order)
}

// Walk provides the same functionality as Map.Walk, but callbacks the walk
// function only with the key.
func (k KeySet[K]) Walk(ctx context.Context, ranger Ranger[K], walkFunc func(key K) (stop bool, err error)) error {
	return (Map[K, NoValue])(k).Walk(ctx, ranger, func(key K, value NoValue) (bool, error) { return walkFunc(key) })
}

// Clear clears the KeySet using the provided Ranger. Refer to Map.Clear for
// behavioral documentation.
func (k KeySet[K]) Clear(ctx context.Context, ranger Ranger[K]) error {
	return (Map[K, NoValue])(k).Clear(ctx, ranger)
}

func (k KeySet[K]) KeyCodec() codec.KeyCodec[K]           { return (Map[K, NoValue])(k).KeyCodec() }
func (k KeySet[K]) ValueCodec() codec.ValueCodec[NoValue] { return (Map[K, NoValue])(k).ValueCodec() }

// KeySetIterator works like an Iterator, but it does not expose any API to deal with values.
type KeySetIterator[K any] Iterator[K, NoValue]

func (i KeySetIterator[K]) Key() (K, error)    { return (Iterator[K, NoValue])(i).Key() }
func (i KeySetIterator[K]) Keys() ([]K, error) { return (Iterator[K, NoValue])(i).Keys() }
func (i KeySetIterator[K]) Next()              { (Iterator[K, NoValue])(i).Next() }
func (i KeySetIterator[K]) Valid() bool        { return (Iterator[K, NoValue])(i).Valid() }
func (i KeySetIterator[K]) Close() error       { return (Iterator[K, NoValue])(i).Close() }

var noValueCodec codec.ValueCodec[NoValue] = NoValue{}

const noValueValueType = "no_value"

// NoValue is a type that can be used to represent a non-existing value.
type NoValue struct{}

func (n NoValue) EncodeJSON(_ NoValue) ([]byte, error) {
	return nil, nil
}

func (n NoValue) DecodeJSON(b []byte) (NoValue, error) {
	if b != nil {
		return NoValue{}, fmt.Errorf("%w: expected nil json bytes, got: %x", ErrEncoding, b)
	}
	return NoValue{}, nil
}

func (NoValue) Encode(_ NoValue) ([]byte, error) {
	return []byte{}, nil
}

func (NoValue) Decode(b []byte) (NoValue, error) {
	if !bytes.Equal(b, []byte{}) {
		return NoValue{}, fmt.Errorf("%w: invalid value, wanted an empty non-nil byte slice, got: %x", ErrEncoding, b)
	}
	return NoValue{}, nil
}

func (NoValue) Stringify(_ NoValue) string {
	return noValueValueType
}

func (n NoValue) ValueType() string {
	return noValueValueType
}
