package morph

import (
	"bytes"
	"encoding/json"

	parseutil "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
	commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
	apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	namespaceTarget  = "namespace"
	containerTarget  = "container"
	jsonFlag         = "json"
	jsonFlagDesc     = "Output rule chains in JSON format"
	chainIDFlag      = "chain-id"
	chainIDDesc      = "Rule chain ID"
	ruleFlag         = "rule"
	ruleFlagDesc     = "Rule chain in text format"
	ruleJSONFlag     = "rule-json"
	ruleJSONFlagDesc = "Chain rule in JSON format or path to the file"
	targetNameFlag   = "target-name"
	targetNameDesc   = "Resource name in APE resource name format"
	targetTypeFlag   = "target-type"
	targetTypeDesc   = "Resource type(container/namespace)"
)

var (
	apeCmd = &cobra.Command{
		Use:   "ape",
		Short: "Section for APE configuration commands",
	}

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

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

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

func initAddRuleChainCmd() {
	apeCmd.AddCommand(addRuleChainCmd)

	addRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
	addRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)

	addRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
	_ = addRuleChainCmd.MarkFlagRequired(targetTypeFlag)
	addRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc)
	_ = addRuleChainCmd.MarkFlagRequired(targetNameFlag)

	addRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc)
	_ = addRuleChainCmd.MarkFlagRequired(chainIDFlag)
	addRuleChainCmd.Flags().String(ruleFlag, "", ruleFlagDesc)
	addRuleChainCmd.Flags().String(ruleJSONFlag, "", ruleJSONFlagDesc)

	addRuleChainCmd.MarkFlagsMutuallyExclusive(ruleFlag, ruleJSONFlag)
}

func initRemoveRuleChainCmd() {
	apeCmd.AddCommand(removeRuleChainCmd)

	removeRuleChainCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
	removeRuleChainCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)

	removeRuleChainCmd.Flags().String(targetTypeFlag, "", targetTypeDesc)
	_ = removeRuleChainCmd.MarkFlagRequired(targetTypeFlag)
	removeRuleChainCmd.Flags().String(targetNameFlag, "", targetNameDesc)
	_ = removeRuleChainCmd.MarkFlagRequired(targetNameFlag)
	removeRuleChainCmd.Flags().String(chainIDFlag, "", chainIDDesc)
	_ = removeRuleChainCmd.MarkFlagRequired(chainIDFlag)
}

func initListRuleChainsCmd() {
	apeCmd.AddCommand(listRuleChainsCmd)

	listRuleChainsCmd.Flags().StringP(endpointFlag, endpointFlagShort, "", endpointFlagDesc)
	listRuleChainsCmd.Flags().String(alphabetWalletsFlag, "", alphabetWalletsFlagDesc)
	listRuleChainsCmd.Flags().StringP(targetTypeFlag, "t", "", targetTypeDesc)
	_ = listRuleChainsCmd.MarkFlagRequired(targetTypeFlag)
	listRuleChainsCmd.Flags().String(targetNameFlag, "", targetNameDesc)
	_ = listRuleChainsCmd.MarkFlagRequired(targetNameFlag)
	listRuleChainsCmd.Flags().Bool(jsonFlag, false, jsonFlagDesc)
}

func addRuleChain(cmd *cobra.Command, _ []string) {
	chain := parseChain(cmd)
	target := parseTarget(cmd)
	pci, ac := newPolicyContractInterface(cmd)
	h, vub, err := pci.AddMorphRuleChain(apechain.Ingress, 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) {
	chainID := parseChainID(cmd)
	target := parseTarget(cmd)
	pci, ac := newPolicyContractInterface(cmd)
	h, vub, err := pci.RemoveMorphRuleChain(apechain.Ingress, 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, _ := newPolicyContractInterface(cmd)
	chains, err := pci.ListMorphRuleChains(apechain.Ingress, 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 {
			parseutil.PrintHumanReadableAPEChain(cmd, c)
		}
	}
}

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)
}