package alphabet

import (
	"crypto/elliptic"

	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/netmap"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"go.uber.org/zap"
)

const emitMethod = "emit"

func (ap *Processor) processEmit() bool {
	index := ap.irList.AlphabetIndex()
	if index < 0 {
		ap.log.Info(logs.AlphabetNonAlphabetModeIgnoreGasEmissionEvent)

		return true
	}

	contract, ok := ap.alphabetContracts.GetByIndex(index)
	if !ok {
		ap.log.Debug(logs.AlphabetNodeIsOutOfAlphabetRangeIgnoreGasEmissionEvent,
			zap.Int("index", index))

		return false
	}

	// there is no signature collecting, so we don't need extra fee
	_, err := ap.morphClient.Invoke(contract, 0, emitMethod)
	if err != nil {
		ap.log.Warn(logs.AlphabetCantInvokeAlphabetEmitMethod, zap.String("error", err.Error()))

		return false
	}

	if ap.storageEmission == 0 {
		ap.log.Info(logs.AlphabetStorageNodeEmissionIsOff)

		return true
	}

	networkMap, err := ap.netmapClient.NetMap()
	if err != nil {
		ap.log.Warn(logs.AlphabetCantGetNetmapSnapshotToEmitGasToStorageNodes,
			zap.String("error", err.Error()))

		return false
	}

	nmNodes := networkMap.Nodes()
	nmLen := len(nmNodes)
	ap.pwLock.RLock()
	pw := ap.parsedWallets
	ap.pwLock.RUnlock()
	extraLen := len(pw)

	ap.log.Debug(logs.AlphabetGasEmission,
		zap.Int("network_map", nmLen),
		zap.Int("extra_wallets", extraLen))

	if nmLen+extraLen == 0 {
		return true
	}

	gasPerNode := fixedn.Fixed8(ap.storageEmission / uint64(nmLen+extraLen))

	ap.transferGasToNetmapNodes(nmNodes, gasPerNode)

	ap.transferGasToExtraNodes(pw, gasPerNode)

	return true
}

func (ap *Processor) transferGasToNetmapNodes(nmNodes []netmap.NodeInfo, gasPerNode fixedn.Fixed8) {
	for i := range nmNodes {
		keyBytes := nmNodes[i].PublicKey()

		key, err := keys.NewPublicKeyFromBytes(keyBytes, elliptic.P256())
		if err != nil {
			ap.log.Warn(logs.AlphabetCantParseNodePublicKey,
				zap.String("error", err.Error()))

			continue
		}

		err = ap.morphClient.TransferGas(key.GetScriptHash(), gasPerNode)
		if err != nil {
			ap.log.Warn(logs.AlphabetCantTransferGas,
				zap.String("receiver", key.Address()),
				zap.Int64("amount", int64(gasPerNode)),
				zap.String("error", err.Error()),
			)
		}
	}
}

func (ap *Processor) transferGasToExtraNodes(pw []util.Uint160, gasPerNode fixedn.Fixed8) {
	if len(pw) > 0 {
		err := ap.morphClient.BatchTransferGas(pw, gasPerNode)
		if err != nil {
			receiversLog := make([]string, len(pw))
			for i, addr := range pw {
				receiversLog[i] = addr.StringLE()
			}
			ap.log.Warn(logs.AlphabetCantTransferGasToWallet,
				zap.Strings("receivers", receiversLog),
				zap.Int64("amount", int64(gasPerNode)),
				zap.String("error", err.Error()),
			)
		}
	}
}