package intermediate

import (
	"crypto/ecdsa"
	"fmt"

	apireputation "github.com/nspcc-dev/neofs-api-go/pkg/reputation"
	"github.com/nspcc-dev/neofs-node/pkg/morph/client/reputation/wrapper"
	"github.com/nspcc-dev/neofs-node/pkg/services/reputation/eigentrust"
	eigentrustcalc "github.com/nspcc-dev/neofs-node/pkg/services/reputation/eigentrust/calculator"
	"github.com/nspcc-dev/neofs-node/pkg/util/logger"
	"go.uber.org/zap"
)

// FinalWriterProviderPrm groups the required parameters of the FinalWriterProvider's constructor.
//
// All values must comply with the requirements imposed on them.
// Passing incorrect parameter values will result in constructor
// failure (error or panic depending on the implementation).
type FinalWriterProviderPrm struct {
	PrivatKey *ecdsa.PrivateKey
	PubKey    []byte
	Client    *wrapper.ClientWrapper
}

// NewFinalWriterProvider creates a new instance of the FinalWriterProvider.
//
// Panics if at least one value of the parameters is invalid.
//
// The created FinalWriterProvider does not require additional
// initialization and is completely ready for work.
func NewFinalWriterProvider(prm FinalWriterProviderPrm, opts ...FinalWriterOption) *FinalWriterProvider {
	o := defaultFinalWriterOptionsOpts()

	for i := range opts {
		opts[i](o)
	}

	return &FinalWriterProvider{
		prm:  prm,
		opts: o,
	}
}

// FinalWriterProvider is implementation of reputation.eigentrust.calculator
// IntermediateWriterProvider interface. It inits FinalWriter.
type FinalWriterProvider struct {
	prm  FinalWriterProviderPrm
	opts *finalWriterOptions
}

func (fwp FinalWriterProvider) InitIntermediateWriter(
	_ eigentrustcalc.Context) (eigentrustcalc.IntermediateWriter, error) {
	return &FinalWriter{
		privatKey: fwp.prm.PrivatKey,
		pubKey:    fwp.prm.PubKey,
		client:    fwp.prm.Client,
		l:         fwp.opts.log,
	}, nil
}

// FinalWriter is implementation of reputation.eigentrust.calculator IntermediateWriter
// interface that writes GlobalTrust to contract directly.
type FinalWriter struct {
	privatKey *ecdsa.PrivateKey
	pubKey    []byte
	client    *wrapper.ClientWrapper

	l *logger.Logger
}

func (fw FinalWriter) WriteIntermediateTrust(t eigentrust.IterationTrust) error {
	args := wrapper.PutArgs{}

	var trustedPublicKey [33]byte
	copy(trustedPublicKey[:], t.Peer().Bytes())

	apiTrustedPeerID := apireputation.NewPeerID()
	apiTrustedPeerID.SetPublicKey(trustedPublicKey)

	apiTrust := apireputation.NewTrust()
	apiTrust.SetValue(t.Value().Float64())
	apiTrust.SetPeer(apiTrustedPeerID)

	var managerPublicKey [33]byte
	copy(managerPublicKey[:], fw.pubKey)

	apiMangerPeerID := apireputation.NewPeerID()
	apiMangerPeerID.SetPublicKey(managerPublicKey)

	gTrust := apireputation.NewGlobalTrust()
	gTrust.SetTrust(apiTrust)
	gTrust.SetManager(apiMangerPeerID)

	err := gTrust.Sign(fw.privatKey)
	if err != nil {
		fw.l.Debug(
			"failed to sign global trust",
			zap.Error(err),
		)
		return fmt.Errorf("failed to sign global trust: %w", err)
	}

	args.SetEpoch(t.Epoch())
	args.SetValue(*gTrust)
	args.SetPeerID(*apiTrustedPeerID)

	err = fw.client.Put(
		args,
	)
	if err != nil {
		fw.l.Debug(
			"failed to write global trust to contract",
			zap.Error(err),
		)
		return fmt.Errorf("failed to write global trust to contract: %w", err)
	}

	fw.l.Debug(
		"sent global trust to contract",
		zap.Uint64("epoch", t.Epoch()),
		zap.Float64("value", t.Value().Float64()),
	)

	return nil
}

type finalWriterOptions struct {
	log *logger.Logger
}

type FinalWriterOption func(*finalWriterOptions)

func defaultFinalWriterOptionsOpts() *finalWriterOptions {
	return &finalWriterOptions{
		log: zap.L(),
	}
}

func FinalWriterWithLogger(l *logger.Logger) FinalWriterOption {
	return func(o *finalWriterOptions) {
		if l != nil {
			o.log = l
		}
	}
}