package native

import (
	"testing"

	"github.com/nspcc-dev/neo-go/pkg/core/dao"
	"github.com/nspcc-dev/neo-go/pkg/core/interop"
	"github.com/nspcc-dev/neo-go/pkg/core/state"
	"github.com/nspcc-dev/neo-go/pkg/core/storage"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
	"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
	"github.com/nspcc-dev/neo-go/pkg/util"
	"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
	"github.com/stretchr/testify/require"
)

func TestDeployGetUpdateDestroyContract(t *testing.T) {
	mgmt := newManagement()
	d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), false))
	err := mgmt.Initialize(&interop.Context{DAO: d})
	require.NoError(t, err)
	script := []byte{byte(opcode.RET)}
	sender := util.Uint160{1, 2, 3}
	ne, err := nef.NewFile(script)
	require.NoError(t, err)
	manif := manifest.NewManifest("Test")
	manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
		Name:       "dummy",
		ReturnType: smartcontract.VoidType,
		Parameters: []manifest.Parameter{},
	})

	h := state.CreateContractHash(sender, ne.Checksum, manif.Name)

	contract, err := mgmt.Deploy(d, sender, ne, manif)
	require.NoError(t, err)
	require.Equal(t, int32(1), contract.ID)
	require.Equal(t, uint16(0), contract.UpdateCounter)
	require.Equal(t, h, contract.Hash)
	require.Equal(t, ne, &contract.NEF)
	require.Equal(t, *manif, contract.Manifest)

	// Double deploy.
	_, err = mgmt.Deploy(d, sender, ne, manif)
	require.Error(t, err)

	// Different sender.
	sender2 := util.Uint160{3, 2, 1}
	contract2, err := mgmt.Deploy(d, sender2, ne, manif)
	require.NoError(t, err)
	require.Equal(t, int32(2), contract2.ID)
	require.Equal(t, uint16(0), contract2.UpdateCounter)
	require.Equal(t, state.CreateContractHash(sender2, ne.Checksum, manif.Name), contract2.Hash)
	require.Equal(t, ne, &contract2.NEF)
	require.Equal(t, *manif, contract2.Manifest)

	refContract, err := mgmt.GetContract(d, h)
	require.NoError(t, err)
	require.Equal(t, contract, refContract)

	upContract, err := mgmt.Update(d, h, ne, manif)
	refContract.UpdateCounter++
	require.NoError(t, err)
	require.Equal(t, refContract, upContract)

	err = mgmt.Destroy(d, h)
	require.NoError(t, err)
	_, err = mgmt.GetContract(d, h)
	require.Error(t, err)
}

func TestManagement_Initialize(t *testing.T) {
	t.Run("good", func(t *testing.T) {
		d := dao.NewSimple(storage.NewMemoryStore(), false)
		mgmt := newManagement()
		require.NoError(t, mgmt.InitializeCache(d))
	})
	t.Run("invalid contract state", func(t *testing.T) {
		d := dao.NewSimple(storage.NewMemoryStore(), false)
		mgmt := newManagement()
		require.NoError(t, d.PutStorageItem(mgmt.ID, []byte{prefixContract}, state.StorageItem{0xFF}))
		require.Error(t, mgmt.InitializeCache(d))
	})
}

func TestManagement_GetNEP17Contracts(t *testing.T) {
	mgmt := newManagement()
	d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), false))
	err := mgmt.Initialize(&interop.Context{DAO: d})
	require.NoError(t, err)

	require.Empty(t, mgmt.GetNEP17Contracts())

	// Deploy NEP17 contract
	script := []byte{byte(opcode.RET)}
	sender := util.Uint160{1, 2, 3}
	ne, err := nef.NewFile(script)
	require.NoError(t, err)
	manif := manifest.NewManifest("Test")
	manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
		Name:       "dummy",
		ReturnType: smartcontract.VoidType,
		Parameters: []manifest.Parameter{},
	})
	manif.SupportedStandards = []string{manifest.NEP17StandardName}
	c1, err := mgmt.Deploy(d, sender, ne, manif)
	require.NoError(t, err)

	// PostPersist is not yet called, thus no NEP17 contracts are expected
	require.Empty(t, mgmt.GetNEP17Contracts())

	// Call PostPersist, check c1 contract hash is returned
	require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
	require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())

	// Update contract
	manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
		Name:       "dummy2",
		ReturnType: smartcontract.VoidType,
		Parameters: []manifest.Parameter{},
	})
	c2, err := mgmt.Update(d, c1.Hash, ne, manif)
	require.NoError(t, err)

	// No changes expected before PostPersist call.
	require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())

	// Call PostPersist, check c2 contract hash is returned
	require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
	require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts())
}