package ape

import (
	"errors"

	"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"
	commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
	apeCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common/ape"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	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"
)

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

func parseTarget(cmd *cobra.Command) policyengine.Target {
	typ := apeCmd.ParseTargetType(cmd)
	name, _ := cmd.Flags().GetString(apeCmd.TargetNameFlag)
	switch typ {
	case policyengine.Namespace:
		if name == "root" {
			name = ""
		}
		return policyengine.NamespaceTarget(name)
	case policyengine.Container:
		var cnr cid.ID
		commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(name))
		return policyengine.ContainerTarget(name)
	case policyengine.User:
		return policyengine.UserTarget(name)
	case policyengine.Group:
		return policyengine.GroupTarget(name)
	default:
		commonCmd.ExitOnErr(cmd, "read target type error: %w", errUnknownTargetType)
	}
	panic("unreachable")
}

// 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.NewRemoteClient(viper.GetViper())
	commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)

	inv := invoker.New(c, nil)
	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.NewRemoteClient(viper.GetViper())
	commonCmd.ExitOnErr(cmd, "unable to create NEO rpc client: %w", err)

	ac, err := helper.NewLocalActor(cmd, c, constants.ConsensusAccountName)
	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
}