package frostfs

import (
	"git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/client/balance"
	frostfsEvent "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/morph/event/frostfs"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"go.uber.org/zap"
)

const (
	// lockAccountLifeTime defines the amount of epochs when lock account is valid.
	lockAccountLifetime uint64 = 20
)

// Process deposit event by invoking a balance contract and sending native
// gas in the sidechain.
func (np *Processor) processDeposit(deposit frostfsEvent.Deposit) bool {
	if !np.alphabetState.IsAlphabet() {
		np.log.Info(logs.FrostFSNonAlphabetModeIgnoreDeposit)
		return true
	}

	prm := balance.MintPrm{}

	prm.SetTo(deposit.To())
	prm.SetAmount(np.converter.ToBalancePrecision(deposit.Amount()))
	prm.SetID(deposit.ID())

	// send transferX to a balance contract
	err := np.balanceClient.Mint(prm)
	if err != nil {
		np.log.Error(logs.FrostFSCantTransferAssetsToBalanceContract, zap.Error(err))
	}

	curEpoch := np.epochState.EpochCounter()
	receiver := deposit.To()

	// check if the receiver has already received some mint GAS emissions
	// we should lock there even though LRU cache is already thread save
	// we lock there because GAS transfer AND cache update must be atomic
	np.mintEmitLock.Lock()
	defer np.mintEmitLock.Unlock()

	val, ok := np.mintEmitCache.Get(receiver.String())
	if ok && val+np.mintEmitThreshold >= curEpoch {
		np.log.Warn(logs.FrostFSDoubleMintEmissionDeclined,
			zap.String("receiver", receiver.String()),
			zap.Uint64("last_emission", val),
			zap.Uint64("current_epoch", curEpoch))

		return false
	}

	// get gas balance of the node
	// before gas transfer check if the balance is greater than the threshold
	balance, err := np.morphClient.GasBalance()
	if err != nil {
		np.log.Error(logs.FrostFSCantGetGasBalanceOfTheNode, zap.Error(err))
		return false
	}

	if balance < np.gasBalanceThreshold {
		np.log.Warn(logs.FrostFSGasBalanceThresholdHasBeenReached,
			zap.Int64("balance", balance),
			zap.Int64("threshold", np.gasBalanceThreshold))

		return false
	}

	err = np.morphClient.TransferGas(receiver, np.mintEmitValue)
	if err != nil {
		np.log.Error(logs.FrostFSCantTransferNativeGasToReceiver,
			zap.String("error", err.Error()))

		return false
	}

	np.mintEmitCache.Add(receiver.String(), curEpoch)

	return true
}

// Process withdraw event by locking assets in the balance account.
func (np *Processor) processWithdraw(withdraw frostfsEvent.Withdraw) bool {
	if !np.alphabetState.IsAlphabet() {
		np.log.Info(logs.FrostFSNonAlphabetModeIgnoreWithdraw)
		return true
	}

	// create lock account
	lock, err := util.Uint160DecodeBytesBE(withdraw.ID()[:util.Uint160Size])
	if err != nil {
		np.log.Error(logs.FrostFSCantCreateLockAccount, zap.Error(err))
		return false
	}

	curEpoch := np.epochState.EpochCounter()

	prm := balance.LockPrm{}

	prm.SetID(withdraw.ID())
	prm.SetUser(withdraw.User())
	prm.SetLock(lock)
	prm.SetAmount(np.converter.ToBalancePrecision(withdraw.Amount()))
	prm.SetDueEpoch(int64(curEpoch + lockAccountLifetime))

	err = np.balanceClient.Lock(prm)
	if err != nil {
		np.log.Error(logs.FrostFSCantLockAssetsForWithdraw, zap.Error(err))
		return false
	}

	return true
}

// Process cheque event by transferring assets from the lock account back to
// the reserve account.
func (np *Processor) processCheque(cheque frostfsEvent.Cheque) bool {
	if !np.alphabetState.IsAlphabet() {
		np.log.Info(logs.FrostFSNonAlphabetModeIgnoreCheque)
		return true
	}

	prm := balance.BurnPrm{}

	prm.SetTo(cheque.LockAccount())
	prm.SetAmount(np.converter.ToBalancePrecision(cheque.Amount()))
	prm.SetID(cheque.ID())

	err := np.balanceClient.Burn(prm)
	if err != nil {
		np.log.Error(logs.FrostFSCantTransferAssetsToFedContract, zap.Error(err))
		return false
	}

	return true
}