package generate

import (
	"bytes"
	"io"
	"math/rand"
	"os"
	"path/filepath"
	"strconv"
	"sync"
	"testing"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/innerring"
	"github.com/nspcc-dev/neo-go/cli/input"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
	"github.com/nspcc-dev/neo-go/pkg/wallet"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/require"
	"golang.org/x/term"
)

func TestGenerateAlphabet(t *testing.T) {
	walletDir := t.TempDir()
	buf := setupTestTerminal(t)

	cmd := GenerateAlphabetCmd
	v := viper.GetViper()

	t.Run("zero size", func(t *testing.T) {
		buf.Reset()
		v.Set(commonflags.AlphabetWalletsFlag, walletDir)
		require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "0"))
		buf.WriteString("pass\r")
		require.Error(t, AlphabetCreds(cmd, nil))
	})
	t.Run("no password provided", func(t *testing.T) {
		buf.Reset()
		v.Set(commonflags.AlphabetWalletsFlag, walletDir)
		require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
		require.Error(t, AlphabetCreds(cmd, nil))
	})
	t.Run("missing directory", func(t *testing.T) {
		buf.Reset()
		dir := filepath.Join(os.TempDir(), "notexist."+strconv.FormatUint(rand.Uint64(), 10))
		v.Set(commonflags.AlphabetWalletsFlag, dir)
		require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
		buf.WriteString("pass\r")
		require.Error(t, AlphabetCreds(cmd, nil))
	})
	t.Run("no password for contract group wallet", func(t *testing.T) {
		buf.Reset()
		v.Set(commonflags.AlphabetWalletsFlag, walletDir)
		require.NoError(t, cmd.Flags().Set(commonflags.AlphabetSizeFlag, "1"))
		buf.WriteString("pass\r")
		require.Error(t, AlphabetCreds(cmd, nil))
	})

	const size = 4

	buf.Reset()
	v.Set(commonflags.AlphabetWalletsFlag, walletDir)
	require.NoError(t, GenerateAlphabetCmd.Flags().Set(commonflags.AlphabetSizeFlag, strconv.FormatUint(size, 10)))
	for i := uint64(0); i < size; i++ {
		buf.WriteString(strconv.FormatUint(i, 10) + "\r")
	}

	buf.WriteString(constants.TestContractPassword + "\r")
	require.NoError(t, AlphabetCreds(GenerateAlphabetCmd, nil))

	var wg sync.WaitGroup
	for i := uint64(0); i < size; i++ {
		i := i
		wg.Add(1)
		go func() {
			defer wg.Done()
			p := filepath.Join(walletDir, innerring.GlagoliticLetter(i).String()+".json")
			w, err := wallet.NewWalletFromFile(p)
			require.NoError(t, err, "wallet doesn't exist")
			require.Equal(t, 3, len(w.Accounts), "not all accounts were created")

			for _, a := range w.Accounts {
				err := a.Decrypt(strconv.FormatUint(i, 10), keys.NEP2ScryptParams())
				require.NoError(t, err, "can't decrypt account")
				switch a.Label {
				case constants.ConsensusAccountName:
					require.Equal(t, smartcontract.GetDefaultHonestNodeCount(size), len(a.Contract.Parameters))
				case constants.CommitteeAccountName:
					require.Equal(t, smartcontract.GetMajorityHonestNodeCount(size), len(a.Contract.Parameters))
				default:
					require.Equal(t, constants.SingleAccountName, a.Label)
				}
			}
		}()
	}
	wg.Wait()

	t.Run("check contract group wallet", func(t *testing.T) {
		p := filepath.Join(walletDir, constants.ContractWalletFilename)
		w, err := wallet.NewWalletFromFile(p)
		require.NoError(t, err, "contract wallet doesn't exist")
		require.Equal(t, 1, len(w.Accounts), "contract wallet must have 1 accout")
		require.NoError(t, w.Accounts[0].Decrypt(constants.TestContractPassword, keys.NEP2ScryptParams()))
	})
}

func setupTestTerminal(t *testing.T) *bytes.Buffer {
	in := bytes.NewBuffer(nil)
	input.Terminal = term.NewTerminal(input.ReadWriter{
		Reader: in,
		Writer: io.Discard,
	}, "")

	t.Cleanup(func() { input.Terminal = nil })

	return in
}