package wallet

import (
	"fmt"
	"os"
	"strings"

	"github.com/manifoldco/promptui"
	"github.com/pkg/errors"
	"github.com/prysmaticlabs/prysm/v4/cmd/validator/flags"
	"github.com/prysmaticlabs/prysm/v4/io/prompt"
	"github.com/prysmaticlabs/prysm/v4/validator/accounts"
	"github.com/prysmaticlabs/prysm/v4/validator/accounts/userprompt"
	"github.com/prysmaticlabs/prysm/v4/validator/accounts/wallet"
	"github.com/prysmaticlabs/prysm/v4/validator/keymanager"
	"github.com/urfave/cli/v2"
)

const (
	// #nosec G101 -- Not sensitive data
	newMnemonicPassphraseYesNoText = "(Advanced) Do you want to setup a '25th word' passphrase for your mnemonic? [y/n]"
	// #nosec G101 -- Not sensitive data
	newMnemonicPassphrasePromptText = "(Advanced) Setup a passphrase '25th word' for your mnemonic " +
		"(WARNING: You cannot recover your keys from your mnemonic if you forget this passphrase!)"
)

func walletCreate(c *cli.Context) error {
	keymanagerKind, err := inputKeymanagerKind(c)
	if err != nil {
		return err
	}

	opts, err := ConstructCLIManagerOpts(c, keymanagerKind)
	if err != nil {
		return err
	}

	acc, err := accounts.NewCLIManager(opts...)
	if err != nil {
		return err
	}
	if _, err := acc.WalletCreate(c.Context); err != nil {
		return errors.Wrap(err, "could not create wallet")
	}
	return nil
}

// ConstructCLIManagerOpts prompts the user for wallet creation input.
func ConstructCLIManagerOpts(cliCtx *cli.Context, keymanagerKind keymanager.Kind) ([]accounts.Option, error) {
	cliOpts := []accounts.Option{}
	// Get wallet dir and check that no wallet exists at the location.
	walletDir, err := userprompt.InputDirectory(cliCtx, userprompt.WalletDirPromptText, flags.WalletDirFlag)
	if err != nil {
		return []accounts.Option{}, err
	}
	dirExists, err := wallet.Exists(walletDir)
	if err != nil {
		return []accounts.Option{}, err
	}
	if dirExists {
		return []accounts.Option{}, errors.New("a wallet already exists at this location. Please input an" +
			" alternative location for the new wallet or remove the current wallet")
	}

	walletPassword, err := prompt.InputPassword(
		cliCtx,
		flags.WalletPasswordFileFlag,
		wallet.NewWalletPasswordPromptText,
		wallet.ConfirmPasswordPromptText,
		true, /* Should confirm password */
		prompt.ValidatePasswordInput,
	)
	if err != nil {
		return []accounts.Option{}, err
	}
	cliOpts = append(cliOpts, accounts.WithWalletDir(walletDir))
	cliOpts = append(cliOpts, accounts.WithWalletPassword(walletPassword))
	cliOpts = append(cliOpts, accounts.WithKeymanagerType(keymanagerKind))
	cliOpts = append(cliOpts, accounts.WithSkipMnemonicConfirm(cliCtx.Bool(flags.SkipDepositConfirmationFlag.Name)))
	if cliCtx.IsSet(flags.MnemonicLanguageFlag.Name) {
		cliOpts = append(cliOpts, accounts.WithMnemonicLanguage(cliCtx.String(flags.MnemonicLanguageFlag.Name)))
	}

	skipMnemonic25thWord := cliCtx.IsSet(flags.SkipMnemonic25thWordCheckFlag.Name)
	has25thWordFile := cliCtx.IsSet(flags.Mnemonic25thWordFileFlag.Name)
	if keymanagerKind == keymanager.Derived {
		numAccounts, err := inputNumAccounts(cliCtx)
		if err != nil {
			return []accounts.Option{}, errors.Wrap(err, "could not get number of accounts to generate")
		}
		cliOpts = append(cliOpts, accounts.WithNumAccounts(int(numAccounts)))
	}
	if keymanagerKind == keymanager.Derived && !skipMnemonic25thWord && !has25thWordFile {
		resp, err := prompt.ValidatePrompt(
			os.Stdin, newMnemonicPassphraseYesNoText, prompt.ValidateYesOrNo,
		)
		if err != nil {
			return []accounts.Option{}, errors.Wrap(err, "could not validate choice")
		}
		if strings.EqualFold(resp, "y") {
			mnemonicPassphrase, err := prompt.InputPassword(
				cliCtx,
				flags.Mnemonic25thWordFileFlag,
				newMnemonicPassphrasePromptText,
				"Confirm mnemonic passphrase",
				true, /* Should confirm password */
				func(input string) error {
					if strings.TrimSpace(input) == "" {
						return errors.New("input cannot be empty")
					}
					return nil
				},
			)
			if err != nil {
				return []accounts.Option{}, err
			}
			cliOpts = append(cliOpts, accounts.WithMnemonic25thWord(mnemonicPassphrase))
		}
	}
	if keymanagerKind == keymanager.Web3Signer {
		return []accounts.Option{}, errors.New("web3signer keymanager does not require persistent wallets.")
	}
	return cliOpts, nil
}

func inputKeymanagerKind(cliCtx *cli.Context) (keymanager.Kind, error) {
	if cliCtx.IsSet(flags.KeymanagerKindFlag.Name) {
		return keymanager.ParseKind(cliCtx.String(flags.KeymanagerKindFlag.Name))
	}
	promptSelect := promptui.Select{
		Label: "Select a type of wallet",
		Items: []string{
			wallet.KeymanagerKindSelections[keymanager.Local],
			wallet.KeymanagerKindSelections[keymanager.Derived],
			wallet.KeymanagerKindSelections[keymanager.Web3Signer],
		},
	}
	selection, _, err := promptSelect.Run()
	if err != nil {
		return keymanager.Local, fmt.Errorf("could not select wallet type: %w", userprompt.FormatPromptError(err))
	}
	return keymanager.Kind(selection), nil
}

// CreateAndSaveWalletCli from user input with a desired keymanager. If a
// wallet already exists in the path, it suggests the user alternatives
// such as how to edit their existing wallet configuration.
func CreateAndSaveWalletCli(cliCtx *cli.Context) (*wallet.Wallet, error) {
	keymanagerKind, err := inputKeymanagerKind(cliCtx)
	if err != nil {
		return nil, err
	}
	opts, err := ConstructCLIManagerOpts(cliCtx, keymanagerKind)
	if err != nil {
		return nil, err
	}
	acc, err := accounts.NewCLIManager(opts...)
	if err != nil {
		return nil, err
	}
	w, err := acc.WalletCreate(cliCtx.Context)
	if err != nil {
		return nil, errors.Wrap(err, "could not create wallet")
	}
	return w, nil
}
