package util

import (
	"crypto/ecdsa"
	"errors"
	"fmt"

	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/session"
	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/signature"
	apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
)

type RequestMessage interface {
	GetMetaHeader() *session.RequestMetaHeader
}

// ResponseMessage is an interface of FrostFS response message.
type ResponseMessage interface {
	GetMetaHeader() *session.ResponseMetaHeader
	SetMetaHeader(*session.ResponseMetaHeader)
}

type SignService struct {
	key *ecdsa.PrivateKey
}

var ErrAbortStream = errors.New("abort message stream")

func NewUnarySignService(key *ecdsa.PrivateKey) *SignService {
	return &SignService{
		key: key,
	}
}

// SignResponse response with private key via signature.SignServiceMessage.
// The signature error affects the result depending on the protocol version:
//   - if status return is supported, panics since we cannot return the failed status, because it will not be signed.
//   - otherwise, returns error in order to transport it directly.
func (s *SignService) SignResponse(resp ResponseMessage, err error) error {
	if err != nil {
		setStatusV2(resp, err)
	}

	err = signature.SignServiceMessage(s.key, resp)
	if err != nil {
		return fmt.Errorf("could not sign response: %w", err)
	}

	return nil
}

func (s *SignService) VerifyRequest(req RequestMessage) error {
	if err := signature.VerifyServiceMessage(req); err != nil {
		sigErr := new(apistatus.SignatureVerification)
		sigErr.SetMessage(err.Error())
		return sigErr
	}
	return nil
}

// EnsureNonNilResponse creates an appropriate response struct if it is nil.
func EnsureNonNilResponse[T any](resp *T, err error) (*T, error) {
	if resp != nil {
		return resp, err
	}
	return new(T), err
}

func setStatusV2(resp ResponseMessage, err error) {
	// unwrap error
	for e := errors.Unwrap(err); e != nil; e = errors.Unwrap(err) {
		err = e
	}

	session.SetStatus(resp, apistatus.ToStatusV2(apistatus.ErrToStatus(err)))
}