package actor_test

import (
	"context"
	"encoding/json"
	"os"

	"github.com/nspcc-dev/neo-go/pkg/core/transaction"
	"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
	"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
	sccontext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
	"github.com/nspcc-dev/neo-go/pkg/wallet"
)

func ExampleActor() {
	// No error checking done at all, intentionally.
	w, _ := wallet.NewWalletFromFile("somewhere")
	defer w.Close()

	c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})

	// Create a simple CalledByEntry-scoped actor (assuming there are accounts
	// inside the wallet).
	a, _ := actor.NewSimple(c, w.Accounts[0])

	customContract := util.Uint160{9, 8, 7}
	// Actor has an Invoker inside, so we can perform test invocations, it will
	// have a signer with the first wallet account and CalledByEntry scope.
	res, _ := a.Call(customContract, "method", 1, 2, 3)
	if res.State != vmstate.Halt.String() {
		panic("failed")
	}
	// All of the side-effects in res can be analyzed.

	// Now we want to send the same invocation in a transaction, but we already
	// have the script and a proper system fee for it, therefore SendUncheckedRun
	// can be used.
	txid, vub, _ := a.SendUncheckedRun(res.Script, res.GasConsumed, nil, nil)
	_ = txid
	_ = vub
	// You need to wait for it to persist and then check the on-chain result of it.

	// Now we want to send some transaction, but give it a priority by increasing
	// its network fee, this can be done with Tuned APIs.
	txid, vub, _ = a.SendTunedCall(customContract, "method", nil, func(r *result.Invoke, t *transaction.Transaction) error {
		// This code is run after the test-invocation done by *Call methods.
		// Reuse the default function to check for HALT execution state.
		err := actor.DefaultCheckerModifier(r, t)
		if err != nil {
			return err
		}
		// Some additional checks can be performed right here, but we only
		// want to raise the network fee by ~20%.
		t.NetworkFee += (t.NetworkFee / 5)
		return nil
	}, 1, 2, 3)
	_ = txid
	_ = vub

	// Actor can be used for higher-level wrappers as well, if we want to interact with
	// NEO then [neo] package can accept our Actor and allow to easily use NEO methods.
	neoContract := neo.New(a)
	balance, _ := neoContract.BalanceOf(a.Sender())
	_ = balance

	// Now suppose the second wallet account is a committee account. We want to
	// create and sign transactions for committee, but use the first account as
	// a sender (because committee account has no GAS). We at the same time want
	// to make all transactions using this actor high-priority ones, because
	// committee can use this attribute.

	// Get the default options to have CheckerModifier/Modifier set up correctly.
	opts := actor.NewDefaultOptions()
	// And override attributes.
	opts.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}}

	// Create an Actor.
	a, _ = actor.NewTuned(c, []actor.SignerAccount{{
		// Sender, regular account with None scope.
		Signer: transaction.Signer{
			Account: w.Accounts[0].ScriptHash(),
			Scopes:  transaction.None,
		},
		Account: w.Accounts[0],
	}, {
		// Commmitee.
		Signer: transaction.Signer{
			Account: w.Accounts[1].ScriptHash(),
			Scopes:  transaction.CalledByEntry,
		},
		Account: w.Accounts[1],
	}}, opts)

	// Use policy contract wrapper to simplify things. All changes in the
	// Policy contract are made by the committee.
	policyContract := policy.New(a)

	// Create a transaction to set storage price, it'll be high-priority and have two
	// signers from above. Committee is a multisignature account, so we can't sign/send
	// it right away, w.Accounts[1] has only one public key. Therefore, we need to
	// create a partially signed transaction and save it, then collect other signatures
	// and send.
	tx, _ := policyContract.SetStoragePriceUnsigned(10)

	net := a.GetNetwork()
	scCtx := sccontext.NewParameterContext(sccontext.TransactionType, net, tx)
	sign := w.Accounts[0].SignHashable(net, tx)
	_ = scCtx.AddSignature(w.Accounts[0].ScriptHash(), w.Accounts[0].Contract, w.Accounts[0].PublicKey(), sign)

	sign = w.Accounts[1].SignHashable(net, tx)
	_ = scCtx.AddSignature(w.Accounts[1].ScriptHash(), w.Accounts[1].Contract, w.Accounts[1].PublicKey(), sign)

	data, _ := json.Marshal(scCtx)
	_ = os.WriteFile("tx.json", data, 0644)

	// Signature collection is out of scope, usually it's manual for cases like this.
}