package ape

import (
	"bytes"
	"encoding/json"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/commonflags"
	commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
	apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
	apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	jsonFlag      = "json"
	jsonFlagDesc  = "Output rule chains in JSON format"
	addrAdminFlag = "addr"
	addrAdminDesc = "The address of the admins wallet"
)

var (
	addRuleChainCmd = &cobra.Command{
		Use:   "add-rule-chain",
		Short: "Add rule chain",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
			_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
		},
		Run: addRuleChain,
	}

	removeRuleChainCmd = &cobra.Command{
		Use:   "rm-rule-chain",
		Short: "Remove rule chain",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
			_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
		},
		Run: removeRuleChain,
	}

	listRuleChainsCmd = &cobra.Command{
		Use:   "list-rule-chains",
		Short: "List rule chains",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
		},
		Run: listRuleChains,
	}

	setAdminCmd = &cobra.Command{
		Use:   "set-admin",
		Short: "Set admin",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
			_ = viper.BindPFlag(commonflags.AlphabetWalletsFlag, cmd.Flags().Lookup(commonflags.AlphabetWalletsFlag))
		},
		Run: setAdmin,
	}

	getAdminCmd = &cobra.Command{
		Use:   "get-admin",
		Short: "Get admin",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
		},
		Run: getAdmin,
	}

	listTargetsCmd = &cobra.Command{
		Use:   "list-targets",
		Short: "List targets",
		PreRun: func(cmd *cobra.Command, _ []string) {
			_ = viper.BindPFlag(commonflags.EndpointFlag, cmd.Flags().Lookup(commonflags.EndpointFlag))
		},
		Run: listTargets,
	}
)

func initAddRuleChainCmd() {
	Cmd.AddCommand(addRuleChainCmd)

	addRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
	addRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)

	addRuleChainCmd.Flags().String(apeCmd.TargetTypeFlag, "", apeCmd.TargetTypeFlagDesc)
	_ = addRuleChainCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
	addRuleChainCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetTypeFlagDesc)
	_ = addRuleChainCmd.MarkFlagRequired(apeCmd.TargetNameFlag)

	addRuleChainCmd.Flags().String(apeCmd.ChainIDFlag, "", apeCmd.ChainIDFlagDesc)
	_ = addRuleChainCmd.MarkFlagRequired(apeCmd.ChainIDFlag)
	addRuleChainCmd.Flags().StringArray(apeCmd.RuleFlag, []string{}, apeCmd.RuleFlagDesc)
	addRuleChainCmd.Flags().String(apeCmd.PathFlag, "", apeCmd.PathFlagDesc)
	addRuleChainCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.ChainNameFlagDesc)
	addRuleChainCmd.MarkFlagsMutuallyExclusive(apeCmd.RuleFlag, apeCmd.PathFlag)
}

func initRemoveRuleChainCmd() {
	Cmd.AddCommand(removeRuleChainCmd)

	removeRuleChainCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
	removeRuleChainCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)

	removeRuleChainCmd.Flags().String(apeCmd.TargetTypeFlag, "", apeCmd.TargetTypeFlagDesc)
	_ = removeRuleChainCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
	removeRuleChainCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetNameFlagDesc)
	_ = removeRuleChainCmd.MarkFlagRequired(apeCmd.TargetNameFlag)
	removeRuleChainCmd.Flags().String(apeCmd.ChainIDFlag, "", apeCmd.ChainIDFlagDesc)
	removeRuleChainCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.ChainNameFlagDesc)
	removeRuleChainCmd.Flags().Bool(commonflags.AllFlag, false, "Remove all chains for target")
	removeRuleChainCmd.MarkFlagsMutuallyExclusive(commonflags.AllFlag, apeCmd.ChainIDFlag)
}

func initListRuleChainsCmd() {
	Cmd.AddCommand(listRuleChainsCmd)

	listRuleChainsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
	listRuleChainsCmd.Flags().StringP(apeCmd.TargetTypeFlag, "t", "", apeCmd.TargetTypeFlagDesc)
	_ = listRuleChainsCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
	listRuleChainsCmd.Flags().String(apeCmd.TargetNameFlag, "", apeCmd.TargetNameFlagDesc)
	listRuleChainsCmd.Flags().Bool(jsonFlag, false, jsonFlagDesc)
	listRuleChainsCmd.Flags().String(apeCmd.ChainNameFlag, apeCmd.Ingress, apeCmd.ChainNameFlagDesc)
}

func initSetAdminCmd() {
	Cmd.AddCommand(setAdminCmd)

	setAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
	setAdminCmd.Flags().String(commonflags.AlphabetWalletsFlag, "", commonflags.AlphabetWalletsFlagDesc)
	setAdminCmd.Flags().String(addrAdminFlag, "", addrAdminDesc)
	_ = setAdminCmd.MarkFlagRequired(addrAdminFlag)
}

func initGetAdminCmd() {
	Cmd.AddCommand(getAdminCmd)

	getAdminCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
}

func initListTargetsCmd() {
	Cmd.AddCommand(listTargetsCmd)

	listTargetsCmd.Flags().StringP(commonflags.EndpointFlag, commonflags.EndpointFlagShort, "", commonflags.EndpointFlagDesc)
	listTargetsCmd.Flags().StringP(apeCmd.TargetTypeFlag, "t", "", apeCmd.TargetTypeFlagDesc)
	_ = listTargetsCmd.MarkFlagRequired(apeCmd.TargetTypeFlag)
}

func addRuleChain(cmd *cobra.Command, _ []string) {
	chain := apeCmd.ParseChain(cmd)
	target := parseTarget(cmd)
	pci, ac := newPolicyContractInterface(cmd)
	h, vub, err := pci.AddMorphRuleChain(apeCmd.ParseChainName(cmd), target, chain)
	cmd.Println("Waiting for transaction to persist...")
	_, err = ac.Wait(h, vub, err)
	commonCmd.ExitOnErr(cmd, "add rule chain error: %w", err)
	cmd.Println("Rule chain added successfully")
}

func removeRuleChain(cmd *cobra.Command, _ []string) {
	target := parseTarget(cmd)
	pci, ac := newPolicyContractInterface(cmd)
	removeAll, _ := cmd.Flags().GetBool(commonflags.AllFlag)
	if removeAll {
		h, vub, err := pci.RemoveMorphRuleChainsByTarget(apeCmd.ParseChainName(cmd), target)
		cmd.Println("Waiting for transaction to persist...")
		_, err = ac.Wait(h, vub, err)
		commonCmd.ExitOnErr(cmd, "remove rule chain error: %w", err)
		cmd.Println("All chains for target removed successfully")
	} else {
		chainID := apeCmd.ParseChainID(cmd)
		h, vub, err := pci.RemoveMorphRuleChain(apeCmd.ParseChainName(cmd), target, chainID)
		cmd.Println("Waiting for transaction to persist...")
		_, err = ac.Wait(h, vub, err)
		commonCmd.ExitOnErr(cmd, "remove rule chain error: %w", err)
		cmd.Println("Rule chain removed successfully")
	}
}

func listRuleChains(cmd *cobra.Command, _ []string) {
	target := parseTarget(cmd)
	pci, _ := newPolicyContractReaderInterface(cmd)
	chains, err := pci.ListMorphRuleChains(apeCmd.ParseChainName(cmd), target)
	commonCmd.ExitOnErr(cmd, "list rule chains error: %w", err)
	if len(chains) == 0 {
		return
	}

	toJSON, _ := cmd.Flags().GetBool(jsonFlag)
	if toJSON {
		prettyJSONFormat(cmd, chains)
	} else {
		for _, c := range chains {
			apeCmd.PrintHumanReadableAPEChain(cmd, c)
		}
	}
}

func setAdmin(cmd *cobra.Command, _ []string) {
	s, _ := cmd.Flags().GetString(addrAdminFlag)
	addr, err := util.Uint160DecodeStringLE(s)
	commonCmd.ExitOnErr(cmd, "can't decode admin addr: %w", err)
	pci, ac := newPolicyContractInterface(cmd)
	h, vub, err := pci.SetAdmin(addr)
	cmd.Println("Waiting for transaction to persist...")
	_, err = ac.Wait(h, vub, err)
	commonCmd.ExitOnErr(cmd, "can't set admin: %w", err)
	cmd.Println("Admin set successfully")
}

func getAdmin(cmd *cobra.Command, _ []string) {
	pci, _ := newPolicyContractReaderInterface(cmd)
	addr, err := pci.GetAdmin()
	commonCmd.ExitOnErr(cmd, "unable to get admin: %w", err)
	cmd.Println(addr.StringLE())
}

func listTargets(cmd *cobra.Command, _ []string) {
	typ := apeCmd.ParseTargetType(cmd)
	pci, inv := newPolicyContractReaderInterface(cmd)

	sid, it, err := pci.ListTargetsIterator(typ)
	commonCmd.ExitOnErr(cmd, "list targets error: %w", err)
	items, err := inv.TraverseIterator(sid, &it, 0)
	for err == nil && len(items) != 0 {
		for _, item := range items {
			bts, err := item.TryBytes()
			commonCmd.ExitOnErr(cmd, "list targets error: %w", err)
			if len(bts) == 0 {
				cmd.Println("(no name)")
			} else {
				cmd.Println(string(bts))
			}
		}
		items, err = inv.TraverseIterator(sid, &it, 0)
		commonCmd.ExitOnErr(cmd, "unable to list targets: %w", err)
	}
}

func prettyJSONFormat(cmd *cobra.Command, chains []*apechain.Chain) {
	wr := bytes.NewBufferString("")
	data, err := json.Marshal(chains)
	if err == nil {
		err = json.Indent(wr, data, "", " ")
	}
	commonCmd.ExitOnErr(cmd, "print rule chain error: %w", err)
	cmd.Println(wr)
}