package config

import (
	"bytes"
	"fmt"
	"os"
	"path/filepath"
	"text/template"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
	"github.com/nspcc-dev/neo-go/cli/input"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

type configTemplate struct {
	Endpoint                string
	AlphabetDir             string
	MaxObjectSize           int
	EpochDuration           int
	CandidateFee            int
	ContainerFee            int
	ContainerAliasFee       int
	WithdrawFee             int
	Glagolitics             []string
	HomomorphicHashDisabled bool
}

const configTxtTemplate = `rpc-endpoint: {{ .Endpoint}}
alphabet-wallets: {{ .AlphabetDir}}
network:
  max_object_size: {{ .MaxObjectSize}}
  epoch_duration: {{ .EpochDuration}}
  homomorphic_hash_disabled: {{ .HomomorphicHashDisabled}}
  fee:
    candidate: {{ .CandidateFee}}
    container: {{ .ContainerFee}}
    container_alias: {{ .ContainerAliasFee }}
    withdraw: {{ .WithdrawFee}}
# if credentials section is omitted, then frostfs-adm will require manual password input
credentials:
  contract: password # wallet for contract group signature{{ range.Glagolitics}}
  {{.}}: password{{end}}
`

func initConfig(cmd *cobra.Command, _ []string) error {
	configPath, err := readConfigPathFromArgs(cmd)
	if err != nil {
		return nil
	}

	pathDir := filepath.Dir(configPath)
	err = os.MkdirAll(pathDir, 0o700)
	if err != nil {
		return fmt.Errorf("create dir %s: %w", pathDir, err)
	}

	f, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_SYNC, 0o600)
	if err != nil {
		return fmt.Errorf("open %s: %w", configPath, err)
	}
	defer f.Close()

	configText, err := generateConfigExample(pathDir, 7)
	if err != nil {
		return err
	}

	_, err = f.WriteString(configText)
	if err != nil {
		return fmt.Errorf("writing to %s: %w", configPath, err)
	}

	cmd.Printf("Initial config file saved to %s\n", configPath)

	return nil
}

func readConfigPathFromArgs(cmd *cobra.Command) (string, error) {
	configPath, err := cmd.Flags().GetString(configPathFlag)
	if err != nil {
		return "", err
	}

	if configPath != "" {
		return configPath, nil
	}

	return defaultConfigPath()
}

func defaultConfigPath() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", fmt.Errorf("getting home dir path: %w", err)
	}

	return filepath.Join(home, ".frostfs", "adm", "config.yml"), nil
}

// generateConfigExample builds .yml representation of the config file. It is
// easier to build it manually with template instead of using viper, because we
// want to order records in specific order in file and, probably, provide
// some comments as well.
func generateConfigExample(appDir string, credSize int) (string, error) {
	tmpl := configTemplate{
		Endpoint:                "https://neo.rpc.node:30333",
		MaxObjectSize:           67108864,      // 64 MiB
		EpochDuration:           240,           // 1 hour with 15s per block
		HomomorphicHashDisabled: false,         // object homomorphic hash is enabled
		CandidateFee:            100_0000_0000, // 100.0 GAS (Fixed8)
		ContainerFee:            1000,          // 0.000000001 * 7 GAS per container (Fixed12)
		ContainerAliasFee:       500,           // ContainerFee / 2
		WithdrawFee:             1_0000_0000,   // 1.0 GAS (Fixed8)
		Glagolitics:             make([]string, 0, credSize),
	}

	appDir, err := filepath.Abs(appDir)
	if err != nil {
		return "", fmt.Errorf("making absolute path for %s: %w", appDir, err)
	}
	tmpl.AlphabetDir = filepath.Join(appDir, "alphabet-wallets")

	var i innerring.GlagoliticLetter
	for i = 0; i < innerring.GlagoliticLetter(credSize); i++ {
		tmpl.Glagolitics = append(tmpl.Glagolitics, i.String())
	}

	t, err := template.New("config.yml").Parse(configTxtTemplate)
	if err != nil {
		return "", fmt.Errorf("parsing config template: %w", err)
	}

	buf := bytes.NewBuffer(nil)

	err = t.Execute(buf, tmpl)
	if err != nil {
		return "", fmt.Errorf("generating config from template: %w", err)
	}

	return buf.String(), nil
}

func GetPassword(v *viper.Viper, name string) (string, error) {
	key := "credentials." + name
	if v.IsSet(key) {
		return v.GetString(key), nil
	}

	prompt := "Password for " + name + " wallet > "
	return input.ReadPassword(prompt)
}

func GetStoragePassword(v *viper.Viper, name string) (string, error) {
	key := "storage." + name
	if name != "" && v.IsSet(key) {
		return v.GetString(key), nil
	}
	return input.ReadPassword("New password > ")
}