package rpcclient

// Various non-policy things from native contracts.

import (
	"crypto/elliptic"
	"errors"
	"fmt"

	"github.com/google/uuid"
	"github.com/nspcc-dev/neo-go/pkg/config"
	"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
	"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)

// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
//
// Deprecated: please use oracle subpackage.
func (c *Client) GetOraclePrice() (int64, error) {
	oracleHash, err := c.GetNativeContractHash(nativenames.Oracle)
	if err != nil {
		return 0, fmt.Errorf("failed to get native Oracle hash: %w", err)
	}
	return c.invokeNativeGetMethod(oracleHash, "getPrice")
}

// GetNNSPrice invokes `getPrice` method on a NeoNameService contract with the specified hash.
//
// Deprecated: please use nns subpackage. This method will be removed in future versions.
func (c *Client) GetNNSPrice(nnsHash util.Uint160) (int64, error) {
	return c.invokeNativeGetMethod(nnsHash, "getPrice")
}

// GetGasPerBlock invokes `getGasPerBlock` method on a native NEO contract.
//
// Deprecated: please use neo subpackage. This method will be removed in future releases.
func (c *Client) GetGasPerBlock() (int64, error) {
	return c.getFromNEO("getGasPerBlock")
}

// GetCandidateRegisterPrice invokes `getRegisterPrice` method on native NEO contract.
//
// Deprecated: please use neo subpackage. This method will be removed in future releases.
func (c *Client) GetCandidateRegisterPrice() (int64, error) {
	return c.getFromNEO("getRegisterPrice")
}

func (c *Client) getFromNEO(meth string) (int64, error) {
	neoHash, err := c.GetNativeContractHash(nativenames.Neo)
	if err != nil {
		return 0, fmt.Errorf("failed to get native NEO hash: %w", err)
	}
	return c.invokeNativeGetMethod(neoHash, meth)
}

// GetDesignatedByRole invokes `getDesignatedByRole` method on a native RoleManagement contract.
//
// Deprecated: please use rolemgmt package.
func (c *Client) GetDesignatedByRole(role noderoles.Role, index uint32) (keys.PublicKeys, error) {
	rmHash, err := c.GetNativeContractHash(nativenames.Designation)
	if err != nil {
		return nil, fmt.Errorf("failed to get native RoleManagement hash: %w", err)
	}
	arr, err := unwrap.Array(c.reader.Call(rmHash, "getDesignatedByRole", int64(role), index))
	if err != nil {
		return nil, err
	}
	pks := make(keys.PublicKeys, len(arr))
	for i, item := range arr {
		val, err := item.TryBytes()
		if err != nil {
			return nil, fmt.Errorf("invalid array element #%d: %s", i, item.Type())
		}
		pks[i], err = keys.NewPublicKeyFromBytes(val, elliptic.P256())
		if err != nil {
			return nil, err
		}
	}
	return pks, nil
}

// NNSResolve invokes `resolve` method on a NameService contract with the specified hash.
//
// Deprecated: please use nns subpackage. This method will be removed in future versions.
func (c *Client) NNSResolve(nnsHash util.Uint160, name string, typ nns.RecordType) (string, error) {
	if typ == nns.CNAME {
		return "", errors.New("can't resolve CNAME record type")
	}
	return unwrap.UTF8String(c.reader.Call(nnsHash, "resolve", name, int64(typ)))
}

// NNSIsAvailable invokes `isAvailable` method on a NeoNameService contract with the specified hash.
//
// Deprecated: please use nns subpackage. This method will be removed in future versions.
func (c *Client) NNSIsAvailable(nnsHash util.Uint160, name string) (bool, error) {
	return unwrap.Bool(c.reader.Call(nnsHash, "isAvailable", name))
}

// NNSGetAllRecords returns iterator over records for a given name from NNS service.
// First return value is the session ID, the second one is Iterator itself, the
// third one is an error. Use TraverseIterator method to traverse iterator values or
// TerminateSession to terminate opened iterator session. See TraverseIterator and
// TerminateSession documentation for more details.
//
// Deprecated: please use nns subpackage. This method will be removed in future versions.
func (c *Client) NNSGetAllRecords(nnsHash util.Uint160, name string) (uuid.UUID, result.Iterator, error) {
	return unwrap.SessionIterator(c.reader.Call(nnsHash, "getAllRecords", name))
}

// NNSUnpackedGetAllRecords returns a set of records for a given name from NNS service
// (config.DefaultMaxIteratorResultItems at max). It differs from NNSGetAllRecords in
// that no iterator session is used to retrieve values from iterator. Instead, unpacking
// VM script is created and invoked via `invokescript` JSON-RPC call.
//
// Deprecated: please use nns subpackage. This method will be removed in future versions.
func (c *Client) NNSUnpackedGetAllRecords(nnsHash util.Uint160, name string) ([]nns.RecordState, error) {
	arr, err := unwrap.Array(c.reader.CallAndExpandIterator(nnsHash, "getAllRecords", config.DefaultMaxIteratorResultItems, name))
	if err != nil {
		return nil, err
	}
	res := make([]nns.RecordState, len(arr))
	for i := range arr {
		rs, ok := arr[i].Value().([]stackitem.Item)
		if !ok {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: not a struct", i)
		}
		if len(rs) != 3 {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: wrong number of elements", i)
		}
		name, err := rs[0].TryBytes()
		if err != nil {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
		}
		typ, err := rs[1].TryInteger()
		if err != nil {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
		}
		data, err := rs[2].TryBytes()
		if err != nil {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: %w", i, err)
		}
		u64Typ := typ.Uint64()
		if !typ.IsUint64() || u64Typ > 255 {
			return nil, fmt.Errorf("failed to decode RecordState from stackitem #%d: bad type", i)
		}
		res[i] = nns.RecordState{
			Name: string(name),
			Type: nns.RecordType(u64Typ),
			Data: string(data),
		}
	}
	return res, nil
}

// GetNotaryServiceFeePerKey returns a reward per notary request key for the designated
// notary nodes. It doesn't cache the result.
//
// Deprecated: please use the Notary contract wrapper from the notary subpackage. This
// method will be removed in future versions.
func (c *Client) GetNotaryServiceFeePerKey() (int64, error) {
	notaryHash, err := c.GetNativeContractHash(nativenames.Notary)
	if err != nil {
		return 0, fmt.Errorf("failed to get native Notary hash: %w", err)
	}
	return c.invokeNativeGetMethod(notaryHash, "getNotaryServiceFeePerKey")
}