package userprompt

import (
	"fmt"
	"os"
	"strings"

	"github.com/logrusorgru/aurora"
	"github.com/manifoldco/promptui"
	"github.com/pkg/errors"
	"github.com/prysmaticlabs/prysm/cmd/validator/flags"
	"github.com/prysmaticlabs/prysm/io/file"
	"github.com/prysmaticlabs/prysm/io/prompt"
	"github.com/prysmaticlabs/prysm/validator/keymanager/remote"
	"github.com/urfave/cli/v2"
)

const (
	// ImportKeysDirPromptText for the import keys cli function.
	ImportKeysDirPromptText = "Enter the directory or filepath where your keystores to import are located"
	// DataDirDirPromptText for the validator database directory.
	DataDirDirPromptText = "Enter the directory of the validator database you would like to use"
	// SlashingProtectionJSONPromptText for the EIP-3076 slashing protection JSON userprompt.
	SlashingProtectionJSONPromptText = "Enter the the filepath of your EIP-3076 Slashing Protection JSON from your previously used validator client"
	// WalletDirPromptText for the wallet.
	WalletDirPromptText = "Enter a wallet directory"
	// SelectAccountsDeletePromptText --
	SelectAccountsDeletePromptText = "Select the account(s) you would like to delete"
	// SelectAccountsBackupPromptText --
	SelectAccountsBackupPromptText = "Select the account(s) you wish to backup"
	// SelectAccountsVoluntaryExitPromptText --
	SelectAccountsVoluntaryExitPromptText = "Select the account(s) on which you wish to perform a voluntary exit"
)

var au = aurora.NewAurora(true)

// InputDirectory from the cli.
func InputDirectory(cliCtx *cli.Context, promptText string, flag *cli.StringFlag) (string, error) {
	directory := cliCtx.String(flag.Name)
	if cliCtx.IsSet(flag.Name) {
		return file.ExpandPath(directory)
	}
	// Append and log the appropriate directory name depending on the flag used.
	if flag.Name == flags.WalletDirFlag.Name {
		ok, err := file.HasDir(directory)
		if err != nil {
			return "", errors.Wrapf(err, "could not check if wallet dir %s exists", directory)
		}
		if ok {
			log.Infof("%s %s", au.BrightMagenta("(wallet path)"), directory)
			return directory, nil
		}
	}

	inputtedDir, err := prompt.DefaultPrompt(au.Bold(promptText).String(), directory)
	if err != nil {
		return "", err
	}
	if inputtedDir == directory {
		return directory, nil
	}
	return file.ExpandPath(inputtedDir)
}

// InputRemoteKeymanagerConfig via the cli.
func InputRemoteKeymanagerConfig(cliCtx *cli.Context) (*remote.KeymanagerOpts, error) {
	addr := cliCtx.String(flags.GrpcRemoteAddressFlag.Name)
	requireTls := !cliCtx.Bool(flags.DisableRemoteSignerTlsFlag.Name)
	crt := cliCtx.String(flags.RemoteSignerCertPathFlag.Name)
	key := cliCtx.String(flags.RemoteSignerKeyPathFlag.Name)
	ca := cliCtx.String(flags.RemoteSignerCACertPathFlag.Name)
	log.Info("Input desired configuration")
	var err error
	if addr == "" {
		addr, err = prompt.ValidatePrompt(
			os.Stdin,
			"Remote gRPC address (such as host.example.com:4000)",
			prompt.NotEmpty)
		if err != nil {
			return nil, err
		}
	}
	if requireTls && crt == "" {
		crt, err = prompt.ValidatePrompt(
			os.Stdin,
			"Path to TLS crt (such as /path/to/client.crt)",
			validateCertPath)
		if err != nil {
			return nil, err
		}
	}
	if requireTls && key == "" {
		key, err = prompt.ValidatePrompt(
			os.Stdin,
			"Path to TLS key (such as /path/to/client.key)",
			validateCertPath)
		if err != nil {
			return nil, err
		}
	}
	if requireTls && ca == "" {
		ca, err = prompt.ValidatePrompt(
			os.Stdin,
			"Path to certificate authority (CA) crt (such as /path/to/ca.crt)",
			validateCertPath)
		if err != nil {
			return nil, err
		}
	}

	crtPath, keyPath, caPath := "", "", ""
	if crt != "" {
		crtPath, err = file.ExpandPath(strings.TrimRight(crt, "\r\n"))
		if err != nil {
			return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
		}
	}
	if key != "" {
		keyPath, err = file.ExpandPath(strings.TrimRight(key, "\r\n"))
		if err != nil {
			return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
		}
	}
	if ca != "" {
		caPath, err = file.ExpandPath(strings.TrimRight(ca, "\r\n"))
		if err != nil {
			return nil, errors.Wrapf(err, "could not determine absolute path for %s", crt)
		}
	}

	newCfg := &remote.KeymanagerOpts{
		RemoteCertificate: &remote.CertificateConfig{
			RequireTls:     requireTls,
			ClientCertPath: crtPath,
			ClientKeyPath:  keyPath,
			CACertPath:     caPath,
		},
		RemoteAddr: addr,
	}
	fmt.Printf("%s\n", newCfg)
	return newCfg, nil
}

func validateCertPath(input string) error {
	if input == "" {
		return errors.New("crt path cannot be empty")
	}
	if !prompt.IsValidUnicode(input) {
		return errors.New("not valid unicode")
	}
	if !file.FileExists(input) {
		return fmt.Errorf("no crt found at path: %s", input)
	}
	return nil
}

// FormatPromptError for the user.
func FormatPromptError(err error) error {
	switch err {
	case promptui.ErrAbort:
		return errors.New("wallet creation aborted, closing")
	case promptui.ErrInterrupt:
		return errors.New("keyboard interrupt, closing")
	case promptui.ErrEOF:
		return errors.New("no input received, closing")
	default:
		return err
	}
}
