package native

import (
	"crypto/elliptic"
	"errors"
	"math/big"

	"git.marketally.com/tutus-one/tutus-chain/pkg/crypto/keys"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

// gasIndexPair contains the block index together with the generated gas per block.
// It is used to cache NEO GASRecords.
type gasIndexPair struct {
	Index       uint32
	GASPerBlock big.Int
}

// gasRecord contains the history of gas per block changes. It is used only by NEO cache.
type gasRecord []gasIndexPair

type (
	// keyWithVotes is a serialized key with votes balance. It's not deserialized
	// because some uses of it imply serialized-only usage and converting to
	// PublicKey is quite expensive.
	keyWithVotes struct {
		Key   string
		Votes *big.Int
		// UnmarshaledKey contains public key if it was unmarshaled.
		UnmarshaledKey *keys.PublicKey
	}

	keysWithVotes []keyWithVotes
)

// PublicKey unmarshals and returns the public key of k.
func (k keyWithVotes) PublicKey() (*keys.PublicKey, error) {
	if k.UnmarshaledKey != nil {
		return k.UnmarshaledKey, nil
	}
	return keys.NewPublicKeyFromBytes([]byte(k.Key), elliptic.P256())
}

func (k keysWithVotes) toStackItem() stackitem.Item {
	arr := make([]stackitem.Item, len(k))
	for i := range k {
		arr[i] = stackitem.NewStruct([]stackitem.Item{
			stackitem.NewByteArray([]byte(k[i].Key)),
			stackitem.NewBigInteger(k[i].Votes),
		})
	}
	return stackitem.NewArray(arr)
}

// toNotificationItem converts keysWithVotes to a stackitem.Item suitable for use in a notification,
// including public keys only.
func (k keysWithVotes) toNotificationItem() stackitem.Item {
	arr := make([]stackitem.Item, len(k))
	for i := range k {
		arr[i] = stackitem.NewByteArray([]byte(k[i].Key))
	}
	return stackitem.NewArray(arr)
}

func (k *keysWithVotes) fromStackItem(item stackitem.Item) error {
	arr, ok := item.Value().([]stackitem.Item)
	if !ok {
		return errors.New("not an array")
	}

	var kvs = make(keysWithVotes, len(arr))
	for i := range arr {
		s, ok := arr[i].Value().([]stackitem.Item)
		if !ok {
			return errors.New("element is not a struct")
		} else if len(s) < 2 {
			return errors.New("invalid length")
		}
		pub, err := s[0].TryBytes()
		if err != nil {
			return err
		}
		vs, err := s[1].TryInteger()
		if err != nil {
			return err
		}
		kvs[i].Key = string(pub)
		kvs[i].Votes = vs
	}
	*k = kvs
	return nil
}

// Bytes serializes keys with votes slice.
func (k keysWithVotes) Bytes(sc *stackitem.SerializationContext) []byte {
	buf, err := sc.Serialize(k.toStackItem(), false)
	if err != nil {
		panic(err)
	}
	return buf
}

// DecodeBytes deserializes keys and votes slice.
func (k *keysWithVotes) DecodeBytes(data []byte) error {
	it, err := stackitem.Deserialize(data)
	if err != nil {
		return err
	}
	return k.fromStackItem(it)
}
