package apemanager

import (
	"encoding/hex"
	"errors"

	internalclient "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/client"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
	commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
	apeSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ape"
	client_sdk "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	apechain "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	"github.com/spf13/cobra"
)

const (
	chainIDFlag    = "chain-id"
	chainIDHexFlag = "chain-id-hex"
	ruleFlag       = "rule"
	pathFlag       = "path"
)

const (
	targetNameFlag = "target-name"
	targetNameDesc = "Resource name in APE resource name format"
	targetTypeFlag = "target-type"
	targetTypeDesc = "Resource type(container/namespace)"
)

const (
	defaultNamespace = ""
	namespaceTarget  = "namespace"
	containerTarget  = "container"
	userTarget       = "user"
	groupTarget      = "group"
)

var errUnknownTargetType = errors.New("unknown target type")

var addCmd = &cobra.Command{
	Use:   "add",
	Short: "Add rule chain for a target",
	Run:   add,
	PersistentPreRun: func(cmd *cobra.Command, _ []string) {
		commonflags.Bind(cmd)
	},
}

func parseTarget(cmd *cobra.Command) (ct apeSDK.ChainTarget) {
	typ, _ := cmd.Flags().GetString(targetTypeFlag)
	name, _ := cmd.Flags().GetString(targetNameFlag)

	ct.Name = name

	switch typ {
	case namespaceTarget:
		ct.TargetType = apeSDK.TargetTypeNamespace
	case containerTarget:
		var cnr cid.ID
		commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(name))
		ct.TargetType = apeSDK.TargetTypeContainer
	case userTarget:
		ct.TargetType = apeSDK.TargetTypeUser
	case groupTarget:
		ct.TargetType = apeSDK.TargetTypeGroup
	default:
		commonCmd.ExitOnErr(cmd, "read target type error: %w", errUnknownTargetType)
	}
	return ct
}

func parseChain(cmd *cobra.Command) apeSDK.Chain {
	chainID, _ := cmd.Flags().GetString(chainIDFlag)
	hexEncoded, _ := cmd.Flags().GetBool(chainIDHexFlag)

	chainIDRaw := []byte(chainID)

	if hexEncoded {
		var err error
		chainIDRaw, err = hex.DecodeString(chainID)
		commonCmd.ExitOnErr(cmd, "can't decode chain ID as hex: %w", err)
	}

	chain := new(apechain.Chain)
	chain.ID = apechain.ID(chainIDRaw)

	if rules, _ := cmd.Flags().GetStringArray(ruleFlag); len(rules) > 0 {
		commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, rules))
	} else if encPath, _ := cmd.Flags().GetString(pathFlag); encPath != "" {
		commonCmd.ExitOnErr(cmd, "decode binary or json error: %w", util.ParseAPEChainBinaryOrJSON(chain, encPath))
	} else {
		commonCmd.ExitOnErr(cmd, "parser error: %w", errors.New("rule is not passed"))
	}

	cmd.Println("Parsed chain:")
	util.PrintHumanReadableAPEChain(cmd, chain)

	serialized := chain.Bytes()
	return apeSDK.Chain{
		Raw: serialized,
	}
}

func add(cmd *cobra.Command, _ []string) {
	c := parseChain(cmd)

	target := parseTarget(cmd)

	key := key.Get(cmd)
	cli := internalclient.GetSDKClientByFlag(cmd, key, commonflags.RPC)

	res, err := cli.APEManagerAddChain(cmd.Context(), client_sdk.PrmAPEManagerAddChain{
		ChainTarget: target,
		Chain:       c,
	})

	commonCmd.ExitOnErr(cmd, "add chain error: %w", err)

	cmd.Println("Rule has been added.")
	cmd.Println("Chain ID: ", string(res.ChainID))
}

func initAddCmd() {
	commonflags.Init(addCmd)

	ff := addCmd.Flags()
	ff.StringArray(ruleFlag, []string{}, "Rule statement")
	ff.String(pathFlag, "", "Path to encoded chain in JSON or binary format")
	ff.String(chainIDFlag, "", "Assign ID to the parsed chain")
	ff.String(targetNameFlag, "", targetNameDesc)
	ff.String(targetTypeFlag, "", targetTypeDesc)
	_ = addCmd.MarkFlagRequired(targetTypeFlag)
	ff.Bool(chainIDHexFlag, false, "Flag to parse chain ID as hex")

	addCmd.MarkFlagsMutuallyExclusive(pathFlag, ruleFlag)
}