package notary

import (
	"fmt"
	"math/big"
	"strconv"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
	"github.com/nspcc-dev/neo-go/cli/input"
	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/encoding/address"
	"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neo-go/pkg/wallet"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	// defaultNotaryDepositLifetime is an amount of blocks notary deposit stays valid.
	// https://github.com/nspcc-dev/neo-go/blob/master/pkg/core/native/notary.go#L48
	defaultNotaryDepositLifetime = 5760

	walletAccountFlag     = "account"
	notaryDepositTillFlag = "till"
)

func depositNotary(cmd *cobra.Command, _ []string) error {
	w, err := openWallet(cmd)
	if err != nil {
		return err
	}

	accHash := w.GetChangeAddress()
	if addr, err := cmd.Flags().GetString(walletAccountFlag); err == nil {
		accHash, err = address.StringToUint160(addr)
		if err != nil {
			return fmt.Errorf("invalid address: %s", addr)
		}
	}

	acc := w.GetAccount(accHash)
	if acc == nil {
		return fmt.Errorf("can't find account for %s", accHash)
	}

	prompt := fmt.Sprintf("Enter password for %s >", address.Uint160ToString(accHash))
	pass, err := input.ReadPassword(prompt)
	if err != nil {
		return fmt.Errorf("can't get password: %v", err)
	}

	err = acc.Decrypt(pass, keys.NEP2ScryptParams())
	if err != nil {
		return fmt.Errorf("can't unlock account: %v", err)
	}

	gasStr, err := cmd.Flags().GetString(commonflags.RefillGasAmountFlag)
	if err != nil {
		return err
	}
	gasAmount, err := helper.ParseGASAmount(gasStr)
	if err != nil {
		return err
	}

	till := int64(defaultNotaryDepositLifetime)
	tillStr, err := cmd.Flags().GetString(notaryDepositTillFlag)
	if err != nil {
		return err
	}
	if tillStr != "" {
		till, err = strconv.ParseInt(tillStr, 10, 64)
		if err != nil || till <= 0 {
			return fmt.Errorf("notary deposit lifetime must be a positive integer")
		}
	}

	return transferGas(cmd, acc, accHash, gasAmount, till)
}

func transferGas(cmd *cobra.Command, acc *wallet.Account, accHash util.Uint160, gasAmount fixedn.Fixed8, till int64) error {
	c, err := helper.GetN3Client(viper.GetViper())
	if err != nil {
		return err
	}

	if err := helper.CheckNotaryEnabled(c); err != nil {
		return err
	}

	height, err := c.GetBlockCount()
	if err != nil {
		return fmt.Errorf("can't get current height: %v", err)
	}

	act, err := actor.New(c, []actor.SignerAccount{{
		Signer: transaction.Signer{
			Account: acc.Contract.ScriptHash(),
			Scopes:  transaction.Global,
		},
		Account: acc,
	}})
	if err != nil {
		return fmt.Errorf("could not create actor: %w", err)
	}

	gasActor := nep17.New(act, gas.Hash)

	txHash, vub, err := gasActor.Transfer(
		accHash,
		notary.Hash,
		big.NewInt(int64(gasAmount)),
		[]any{nil, int64(height) + till},
	)
	if err != nil {
		return fmt.Errorf("could not send tx: %w", err)
	}

	return helper.AwaitTx(cmd, c, []helper.HashVUBPair{{Hash: txHash, Vub: vub}})
}

func openWallet(cmd *cobra.Command) (*wallet.Wallet, error) {
	p, err := cmd.Flags().GetString(commonflags.StorageWalletFlag)
	if err != nil {
		return nil, err
	} else if p == "" {
		return nil, fmt.Errorf("missing wallet path (use '--%s <out.json>')", commonflags.StorageWalletFlag)
	}

	w, err := wallet.NewWalletFromFile(p)
	if err != nil {
		return nil, fmt.Errorf("can't open wallet: %v", err)
	}
	return w, nil
}