package cli

import (
	"bufio"
	"bytes"
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"

	sdkmath "cosmossdk.io/math"
	"github.com/0glabs/0g-chain/crypto/vrf"
	"github.com/0glabs/0g-chain/x/council/v1/types"
	"github.com/cosmos/cosmos-sdk/client"
	"github.com/cosmos/cosmos-sdk/client/flags"
	"github.com/cosmos/cosmos-sdk/client/tx"
	sdkkr "github.com/cosmos/cosmos-sdk/crypto/keyring"
	sdk "github.com/cosmos/cosmos-sdk/types"
	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

	vrfalgo "github.com/coniks-sys/coniks-go/crypto/vrf"
	"github.com/cosmos/cosmos-sdk/client/input"
	"github.com/spf13/cobra"
)

// GetTxCmd returns the transaction commands for this module
func GetTxCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:                        types.ModuleName,
		Short:                      fmt.Sprintf("%s transactions subcommands", types.ModuleName),
		DisableFlagParsing:         true,
		SuggestionsMinimumDistance: 2,
		RunE:                       client.ValidateCmd,
	}
	cmd.AddCommand(
		NewRegisterCmd(),
		NewVoteCmd(),
	)
	return cmd
}

func NewRegisterCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "register",
		Short: "Register a voter",
		Args:  cobra.NoArgs,
		RunE: func(cmd *cobra.Command, args []string) error {
			// bypass the restriction of set keyring options
			ctx := client.GetClientContextFromCmd(cmd).WithKeyringOptions(vrf.VrfOption())
			client.SetCmdClientContext(cmd, ctx)
			clientCtx, err := client.GetClientTxContext(cmd)
			if err != nil {
				return err
			}

			kr := clientCtx.Keyring
			// get account name by address
			accAddr := clientCtx.GetFromAddress()
			accRecord, err := kr.KeyByAddress(accAddr)
			if err != nil {
				// not found record by address in keyring
				return nil
			}

			// check voter account record exists
			voterAccName := accRecord.Name + "-voter"
			_, err = kr.Key(voterAccName)
			if err == nil {
				// account exists, ask for user confirmation
				response, err2 := input.GetConfirmation(fmt.Sprintf("override the existing name %s", voterAccName), bufio.NewReader(clientCtx.Input), cmd.ErrOrStderr())
				if err2 != nil {
					return err2
				}

				if !response {
					return errors.New("aborted")
				}

				err2 = kr.Delete(voterAccName)
				if err2 != nil {
					return err2
				}
			}

			keyringAlgos, _ := kr.SupportedAlgorithms()
			algo, err := sdkkr.NewSigningAlgoFromString("vrf", keyringAlgos)
			if err != nil {
				return err
			}

			newRecord, err := kr.NewAccount(voterAccName, "", "", "", algo)
			if err != nil {
				return err
			}

			pubKey, err := newRecord.GetPubKey()
			if err != nil {
				return err
			}

			valAddr, err := sdk.ValAddressFromHex(hex.EncodeToString(clientCtx.GetFromAddress().Bytes()))
			if err != nil {
				return err
			}

			msg := &types.MsgRegister{
				Voter: valAddr.String(),
				Key:   pubKey.Bytes(),
			}
			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
		},
	}

	flags.AddTxFlagsToCmd(cmd)
	return cmd
}

func NewVoteCmd() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "vote council-id",
		Short: "Vote on a proposal",
		Args:  cobra.MinimumNArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			clientCtx, err := client.GetClientTxContext(cmd)
			if err != nil {
				return err
			}

			kr := clientCtx.Keyring

			// get account name by address
			inAddr := clientCtx.GetFromAddress()

			valAddr, err := sdk.ValAddressFromHex(hex.EncodeToString(inAddr.Bytes()))
			if err != nil {
				return err
			}

			inRecord, err := kr.KeyByAddress(inAddr)
			if err != nil {
				// not found record by address in keyring
				return nil
			}

			// check voter account record exists
			voterAccName := inRecord.Name + "-voter"
			voterRecord, err := kr.Key(voterAccName)
			if err != nil {
				// not found voter account
				return err
			}
			sk := vrfalgo.PrivateKey(voterRecord.GetLocal().PrivKey.Value)

			councilID, err := strconv.ParseUint(args[0], 10, 64)
			if err != nil {
				return err
			}

			votingStartHeight := types.DefaultVotingStartHeight + (councilID-1)*types.DefaultVotingPeriod

			rsp, err := stakingtypes.NewQueryClient(clientCtx).HistoricalInfo(cmd.Context(), &stakingtypes.QueryHistoricalInfoRequest{Height: int64(votingStartHeight)})
			if err != nil {
				return err
			}

			var tokens sdkmath.Int
			for _, val := range rsp.Hist.Valset {
				thisValAddr := val.GetOperator()

				if thisValAddr.Equals(valAddr) {
					tokens = val.GetTokens()
				}
			}
			// the denom of token is evm denom, need to convert to A0GI
			a0giTokenCnt := tokens.Quo(sdk.NewInt(1_000_000_000_000_000_000))
			// 1_000 0AGI token / vote
			numBallots := a0giTokenCnt.Quo(sdk.NewInt(1_000)).Uint64()
			ballots := make([]*types.Ballot, numBallots)
			for i := range ballots {
				ballotID := uint64(i)
				ballots[i] = &types.Ballot{
					ID:      ballotID,
					Content: sk.Compute(bytes.Join([][]byte{rsp.Hist.Header.LastCommitHash, types.Uint64ToBytes(ballotID)}, nil)),
				}
			}

			msg := &types.MsgVote{
				CouncilID: councilID,
				Voter:     valAddr.String(),
				Ballots:   ballots,
			}
			return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
		},
	}

	flags.AddTxFlagsToCmd(cmd)
	return cmd
}