package morph

import (
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/ape"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/balance"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/config"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/container"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/frostfsid"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/notary"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/policy"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/proxy"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/util"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	alphabetSizeFlag = "size"

	storageWalletLabelFlag = "label"
	storageGasCLIFlag      = "initial-gas"
	storageGasConfigFlag   = "storage.initial_gas"

	maxObjectSizeCLIFlag = "max-object-size"

	epochDurationCLIFlag = "epoch-duration"

	containerFeeCLIFlag      = "container-fee"
	containerAliasFeeCLIFlag = "container-alias-fee"

	candidateFeeCLIFlag = "candidate-fee"

	homomorphicHashDisabledCLIFlag = "homomorphic-disabled"

	withdrawFeeCLIFlag = "withdraw-fee"

	walletAddressFlag = "wallet-address"
)

var (
	// RootCmd is a root command of config section.
	RootCmd = &cobra.Command{
		Use:   "morph",
		Short: "Section for morph network configuration commands",
	}

	generateAlphabetCmd = &cobra.Command{
		Use:   "generate-alphabet",
		Short: "Generate alphabet wallets for consensus nodes of the morph network",
		PreRun: func(cmd *cobra.Command, _ []string) {
			// PreRun fixes https://github.com/spf13/viper/issues/233
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
		},
		RunE: generateAlphabetCreds,
	}

	initCmd = &cobra.Command{
		Use:   "init",
		Short: "Initialize side chain network with smart-contracts and network settings",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
			_ = viper.BindPFlag(util.EpochDurationInitFlag, cmd.Flags().Lookup(epochDurationCLIFlag))
			_ = viper.BindPFlag(util.MaxObjectSizeInitFlag, cmd.Flags().Lookup(maxObjectSizeCLIFlag))
			_ = viper.BindPFlag(util.HomomorphicHashDisabledInitFlag, cmd.Flags().Lookup(homomorphicHashDisabledCLIFlag))
			_ = viper.BindPFlag(util.CandidateFeeInitFlag, cmd.Flags().Lookup(candidateFeeCLIFlag))
			_ = viper.BindPFlag(util.ContainerFeeInitFlag, cmd.Flags().Lookup(containerFeeCLIFlag))
			_ = viper.BindPFlag(util.ContainerAliasFeeInitFlag, cmd.Flags().Lookup(containerAliasFeeCLIFlag))
			_ = viper.BindPFlag(util.WithdrawFeeInitFlag, cmd.Flags().Lookup(withdrawFeeCLIFlag))
			_ = viper.BindPFlag(util.ProtoConfigPath, cmd.Flags().Lookup(util.ProtoConfigPath))
		},
		RunE: initializeSideChainCmd,
	}

	generateStorageCmd = &cobra.Command{
		Use:   "generate-storage-wallet",
		Short: "Generate storage node wallet for the morph network",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
			_ = viper.BindPFlag(storageGasConfigFlag, cmd.Flags().Lookup(storageGasCLIFlag))
		},
		RunE: generateStorageCreds,
	}

	refillGasCmd = &cobra.Command{
		Use:   "refill-gas",
		Short: "Refill GAS of storage node's wallet in the morph network",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
			_ = viper.BindPFlag(util.RefillGasAmountFlag, cmd.Flags().Lookup(util.RefillGasAmountFlag))
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			return refillGas(cmd, util.RefillGasAmountFlag, false)
		},
	}

	forceNewEpoch = &cobra.Command{
		Use:   "force-new-epoch",
		Short: "Create new FrostFS epoch event in the side chain",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
		},
		RunE: forceNewEpochCmd,
	}

	removeNodes = &cobra.Command{
		Use:   "remove-nodes key1 [key2 [...]]",
		Short: "Remove storage nodes from the netmap",
		Long:  `Move nodes to the Offline state in the candidates list and tick an epoch to update the netmap`,
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
		},
		RunE: removeNodesCmd,
	}

	dumpContractHashesCmd = &cobra.Command{
		Use:   "dump-hashes",
		Short: "Dump deployed contract hashes",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
		},
		RunE: dumpContractHashes,
	}

	updateContractsCmd = &cobra.Command{
		Use:   "update-contracts",
		Short: "Update FrostFS contracts",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
		},
		RunE: updateContracts,
	}

	netmapCandidatesCmd = &cobra.Command{
		Use:   "netmap-candidates",
		Short: "List netmap candidates nodes",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(util.EndpointFlag, cmd.Flags().Lookup(util.EndpointFlag))
			_ = viper.BindPFlag(util.AlphabetWalletsFlag, cmd.Flags().Lookup(util.AlphabetWalletsFlag))
		},
		Run: listNetmapCandidatesNodes,
	}
)

func init() {
	initGenerateAlphabetCmd()
	initInitCmd()
	initDeployCmd()
	initGenerateStorageCmd()
	initForceNewEpochCmd()
	initRemoveNodesCmd()
	RootCmd.AddCommand(policy.Set)
	RootCmd.AddCommand(policy.Dump)
	initDumpContractHashesCmd()
	RootCmd.AddCommand(config.SetCmd)
	RootCmd.AddCommand(config.DumpCmd)
	RootCmd.AddCommand(balance.DumpCmd)
	initUpdateContractsCmd()
	RootCmd.AddCommand(container.ListCmd)
	RootCmd.AddCommand(container.RestoreCmd)
	RootCmd.AddCommand(container.DumpCmd)
	initRefillGasCmd()
	RootCmd.AddCommand(notary.DepositCmd)
	initNetmapCandidatesCmd()

	RootCmd.AddCommand(ape.Cmd)
	RootCmd.AddCommand(proxy.AddAccountCmd)
	RootCmd.AddCommand(proxy.RemoveAccountCmd)

	RootCmd.AddCommand(frostfsid.Cmd)
}

func initNetmapCandidatesCmd() {
	RootCmd.AddCommand(netmapCandidatesCmd)
	netmapCandidatesCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
}

func initRefillGasCmd() {
	RootCmd.AddCommand(refillGasCmd)
	refillGasCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	refillGasCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	refillGasCmd.Flags().String(util.StorageWalletFlag, "", "Path to storage node wallet")
	refillGasCmd.Flags().String(walletAddressFlag, "", "Address of wallet")
	refillGasCmd.Flags().String(util.RefillGasAmountFlag, "", "Additional amount of GAS to transfer")
	refillGasCmd.MarkFlagsMutuallyExclusive(walletAddressFlag, util.StorageWalletFlag)
}

func initUpdateContractsCmd() {
	RootCmd.AddCommand(updateContractsCmd)
	updateContractsCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	updateContractsCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	updateContractsCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc)
	updateContractsCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc)
	updateContractsCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag)
}

func initDumpContractHashesCmd() {
	RootCmd.AddCommand(dumpContractHashesCmd)
	dumpContractHashesCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	dumpContractHashesCmd.Flags().String(customZoneFlag, "", "Custom zone to search.")
}

func initRemoveNodesCmd() {
	RootCmd.AddCommand(removeNodes)
	removeNodes.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	removeNodes.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	removeNodes.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file")
}

func initForceNewEpochCmd() {
	RootCmd.AddCommand(forceNewEpoch)
	forceNewEpoch.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	forceNewEpoch.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	forceNewEpoch.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file")
}

func initGenerateStorageCmd() {
	RootCmd.AddCommand(generateStorageCmd)
	generateStorageCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	generateStorageCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	generateStorageCmd.Flags().String(util.StorageWalletFlag, "", "Path to new storage node wallet")
	generateStorageCmd.Flags().String(storageGasCLIFlag, "", "Initial amount of GAS to transfer")
	generateStorageCmd.Flags().StringP(storageWalletLabelFlag, "l", "", "Wallet label")
}

func initInitCmd() {
	RootCmd.AddCommand(initCmd)
	initCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	initCmd.Flags().StringP(util.EndpointFlag, util.EndpointFlagShort, "", util.EndpointFlagDesc)
	initCmd.Flags().String(util.ContractsInitFlag, "", util.ContractsInitFlagDesc)
	initCmd.Flags().String(util.ContractsURLFlag, "", util.ContractsURLFlagDesc)
	initCmd.Flags().Uint(epochDurationCLIFlag, 240, "Amount of side chain blocks in one FrostFS epoch")
	initCmd.Flags().Uint(maxObjectSizeCLIFlag, 67108864, "Max single object size in bytes")
	initCmd.Flags().Bool(homomorphicHashDisabledCLIFlag, false, "Disable object homomorphic hashing")
	// Defaults are taken from neo-preodolenie.
	initCmd.Flags().Uint64(containerFeeCLIFlag, 1000, "Container registration fee")
	initCmd.Flags().Uint64(containerAliasFeeCLIFlag, 500, "Container alias fee")
	initCmd.Flags().String(util.ProtoConfigPath, "", "Path to the consensus node configuration")
	initCmd.Flags().String(util.LocalDumpFlag, "", "Path to the blocks dump file")
	initCmd.MarkFlagsMutuallyExclusive(util.ContractsInitFlag, util.ContractsURLFlag)
}

func initGenerateAlphabetCmd() {
	RootCmd.AddCommand(generateAlphabetCmd)
	generateAlphabetCmd.Flags().String(util.AlphabetWalletsFlag, "", util.AlphabetWalletsFlagDesc)
	generateAlphabetCmd.Flags().Uint(alphabetSizeFlag, 7, "Amount of alphabet wallets to generate")
}

func initDeployCmd() {
	RootCmd.AddCommand(deployCmd)
}