package signature

import (
	"crypto/ecdsa"
	"errors"

	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neofs-sdk-go/signature"
)

type DataSource interface {
	ReadSignedData([]byte) ([]byte, error)
	SignedDataSize() int
}

type DataWithSignature interface {
	DataSource
	GetSignature() *signature.Signature
	SetSignature(*signature.Signature)
}

type SignOption func(*cfg)

const (
	// PrivateKeyCompressedSize is constant with compressed size of private key (SK).
	// D coordinate stored, recover PK by formula x, y = curve.ScalarBaseMul(d,bytes).
	PrivateKeyCompressedSize = 32

	// PublicKeyCompressedSize is constant with compressed size of public key (PK).
	PublicKeyCompressedSize = 33

	// PublicKeyUncompressedSize is constant with uncompressed size of public key (PK).
	// First byte always should be 0x4 other 64 bytes is X and Y (32 bytes per coordinate).
	// 2 * 32 + 1.
	PublicKeyUncompressedSize = 65
)

var (
	// ErrEmptyPrivateKey is returned when used private key is empty.
	ErrEmptyPrivateKey = errors.New("empty private key")
	// ErrInvalidPublicKey is returned when public key cannot be unmarshalled.
	ErrInvalidPublicKey = errors.New("invalid public key")
	// ErrInvalidSignature is returned if signature cannot be verified.
	ErrInvalidSignature = errors.New("invalid signature")
)

func SignData(key *ecdsa.PrivateKey, src DataSource, opts ...SignOption) (res *signature.Signature, err error) {
	err = signDataWithHandler(key, src, func(key, sig []byte, scheme signature.Scheme) {
		res = new(signature.Signature)
		res.SetKey(key)
		res.SetSign(sig)
		res.SetScheme(scheme)
	}, opts...)

	return
}

func VerifyData(dataSrc DataSource, sig *signature.Signature, opts ...SignOption) error {
	return verifyDataWithSource(dataSrc, func() ([]byte, []byte, signature.Scheme) {
		return sig.Key(), sig.Sign(), sig.Scheme()
	}, opts...)
}

func SignDataWithHandler(key *ecdsa.PrivateKey, src DataSource, handler func(key, sig []byte)) error {
	return signDataWithHandler(key, src, func(key, sig []byte, scheme signature.Scheme) {
		handler(key, sig)
	})
}

func signDataWithHandler(
	key *ecdsa.PrivateKey,
	src DataSource,
	handler func(key, sig []byte, scheme signature.Scheme),
	opts ...SignOption,
) error {
	if key == nil {
		return ErrEmptyPrivateKey
	}

	data, err := dataForSignature(src)
	if err != nil {
		return err
	}
	defer bytesPool.Put(&data)

	cfg := getConfig(opts...)

	sigData, err := sign(cfg.scheme, key, data)
	if err != nil {
		return err
	}

	handler((*keys.PublicKey)(&key.PublicKey).Bytes(), sigData, cfg.scheme)

	return nil
}

func VerifyDataWithSource(dataSrc DataSource, sigSrc func() (key, sig []byte)) error {
	return verifyDataWithSource(dataSrc, func() ([]byte, []byte, signature.Scheme) {
		key, sign := sigSrc()
		return key, sign, signature.ECDSAWithSHA512
	})
}

func verifyDataWithSource(
	dataSrc DataSource,
	sigSrc func() (key, sig []byte, scheme signature.Scheme),
	opts ...SignOption,
) error {
	data, err := dataForSignature(dataSrc)
	if err != nil {
		return err
	}
	defer bytesPool.Put(&data)

	cfg := getConfig(opts...)

	return verify(cfg, data, sigSrc)
}