package core

import (
	"context"

	"github.com/nspcc-dev/neofs-api-go/refs"
	"github.com/nspcc-dev/neofs-api-go/service"
	crypto "github.com/nspcc-dev/neofs-crypto"
	"github.com/nspcc-dev/neofs-node/internal"
)

// OwnerKeyContainer is an interface of the container of owner's ID and key pair with read access.
type OwnerKeyContainer interface {
	GetOwnerID() refs.OwnerID
	GetOwnerKey() []byte
}

// OwnerKeyVerifier is an interface of OwnerKeyContainer validator.
type OwnerKeyVerifier interface {
	// Must check if OwnerKeyContainer satisfies a certain criterion.
	// Nil error is equivalent to matching the criterion.
	VerifyKey(context.Context, OwnerKeyContainer) error
}

type neoKeyVerifier struct{}

// ErrNilOwnerKeyContainer is returned by functions that expect a non-nil
// OwnerKeyContainer, but received nil.
const ErrNilOwnerKeyContainer = internal.Error("owner-key container is nil")

// ErrNilOwnerKeyVerifier is returned by functions that expect a non-nil
// OwnerKeyVerifier, but received nil.
const ErrNilOwnerKeyVerifier = internal.Error("owner-key verifier is nil")

// NewNeoKeyVerifier creates a new Neo owner key verifier and return a OwnerKeyVerifier interface.
func NewNeoKeyVerifier() OwnerKeyVerifier {
	return new(neoKeyVerifier)
}

// VerifyKey checks if the public key converts to owner ID.
//
// If passed OwnerKeyContainer is nil, ErrNilOwnerKeyContainer returns.
// If public key cannot be unmarshaled, service.ErrInvalidPublicKeyBytes returns.
// If public key is not converted to owner ID, service.ErrWrongOwner returns.
// With neo:morph adoption public key can be unrelated to owner ID. In this
// case VerifyKey should call NeoFS.ID smart-contract to check whether public
// key is bounded with owner ID. If there is no bound, then return
// service.ErrWrongOwner.
func (s neoKeyVerifier) VerifyKey(_ context.Context, src OwnerKeyContainer) error {
	if src == nil {
		return ErrNilOwnerKeyContainer
	}

	pubKey := crypto.UnmarshalPublicKey(src.GetOwnerKey())
	if pubKey == nil {
		return service.ErrInvalidPublicKeyBytes
	}

	ownerFromKey, err := refs.NewOwnerID(pubKey)
	if err != nil {
		return err
	}

	if !ownerFromKey.Equal(src.GetOwnerID()) {
		return service.ErrWrongOwner
	}

	return nil
}