package ape

import (
	"errors"
	"strings"

	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/constants"
	"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-adm/internal/modules/morph/helper"
	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"
	policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
	morph "git.frostfs.info/TrueCloudLab/policy-engine/pkg/morph/policy"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/spf13/cobra"
	"github.com/spf13/viper"
)

const (
	ingress = "ingress"
	s3      = "s3"
)

var mChainName = map[string]apechain.Name{
	ingress: apechain.Ingress,
	s3:      apechain.S3,
}

var (
	errUnknownTargetType    = errors.New("unknown target type")
	errChainIDCannotBeEmpty = errors.New("chain id cannot be empty")
	errRuleIsNotParsed      = errors.New("rule is not passed")
	errUnsupportedChainName = errors.New("unsupported chain name")
)

func parseTarget(cmd *cobra.Command) policyengine.Target {
	name, _ := cmd.Flags().GetString(targetNameFlag)
	typ, err := parseTargetType(cmd)

	// interpret "root" namespace as empty
	if typ == policyengine.Namespace && name == "root" {
		name = ""
	}

	commonCmd.ExitOnErr(cmd, "read target type error: %w", err)

	return policyengine.Target{
		Name: name,
		Type: typ,
	}
}

func parseTargetType(cmd *cobra.Command) (policyengine.TargetType, error) {
	typ, _ := cmd.Flags().GetString(targetTypeFlag)
	switch typ {
	case namespaceTarget:
		return policyengine.Namespace, nil
	case containerTarget:
		return policyengine.Container, nil
	case userTarget:
		return policyengine.User, nil
	case groupTarget:
		return policyengine.Group, nil
	}
	return -1, errUnknownTargetType
}

func parseChainID(cmd *cobra.Command) apechain.ID {
	chainID, _ := cmd.Flags().GetString(chainIDFlag)
	if chainID == "" {
		commonCmd.ExitOnErr(cmd, "read chain id error: %w",
			errChainIDCannotBeEmpty)
	}
	return apechain.ID(chainID)
}

func parseChain(cmd *cobra.Command) *apechain.Chain {
	chain := new(apechain.Chain)

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

	chain.ID = parseChainID(cmd)

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

	return chain
}

func parseChainName(cmd *cobra.Command) apechain.Name {
	chainName, _ := cmd.Flags().GetString(chainNameFlag)
	apeChainName, ok := mChainName[strings.ToLower(chainName)]
	if !ok {
		commonCmd.ExitOnErr(cmd, "", errUnsupportedChainName)
	}
	return apeChainName
}

// invokerAdapter adapats invoker.Invoker to ContractStorageInvoker interface.
type invokerAdapter struct {
	*invoker.Invoker
	rpcActor invoker.RPCInvoke
}

func (n *invokerAdapter) GetRPCInvoker() invoker.RPCInvoke {
	return n.rpcActor
}

func newPolicyContractReaderInterface(cmd *cobra.Command) (*morph.ContractStorageReader, *invoker.Invoker) {
	c, err := helper.GetN3Client(viper.GetViper())
	commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)

	inv := invoker.New(c, nil)
	var ch util.Uint160
	r := management.NewReader(inv)
	nnsCs, err := helper.GetContractByID(r, 1)
	commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)

	ch, err = helper.NNSResolveHash(inv, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
	commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)

	invokerAdapter := &invokerAdapter{
		Invoker:  inv,
		rpcActor: c,
	}

	return morph.NewContractStorageReader(invokerAdapter, ch), inv
}

func newPolicyContractInterface(cmd *cobra.Command) (*morph.ContractStorage, *helper.LocalActor) {
	c, err := helper.GetN3Client(viper.GetViper())
	commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)

	ac, err := helper.NewLocalActor(cmd, c)
	commonCmd.ExitOnErr(cmd, "can't create actor: %w", err)

	var ch util.Uint160
	r := management.NewReader(ac.Invoker)
	nnsCs, err := helper.GetContractByID(r, 1)
	commonCmd.ExitOnErr(cmd, "can't get NNS contract state: %w", err)

	ch, err = helper.NNSResolveHash(ac.Invoker, nnsCs.Hash, helper.DomainOf(constants.PolicyContract))
	commonCmd.ExitOnErr(cmd, "unable to resolve policy contract hash: %w", err)

	return morph.NewContractStorage(ac, ch), ac
}