package contracts

import (
	"encoding/json"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"testing"

	"github.com/nspcc-dev/neo-go/pkg/core/state"
	"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/stretchr/testify/require"
)

var (
	helper1ContractNEFPath      = filepath.Join("management_helper", "management_helper1.nef")
	helper1ContractManifestPath = filepath.Join("management_helper", "management_helper1.manifest.json")
	helper2ContractNEFPath      = filepath.Join("management_helper", "management_helper2.nef")
	helper2ContractManifestPath = filepath.Join("management_helper", "management_helper2.manifest.json")

	oracleContractModPath      = "oracle_contract"
	oracleContractYAMLPath     = filepath.Join(oracleContractModPath, "oracle.yml")
	oracleContractNEFPath      = filepath.Join(oracleContractModPath, "oracle.nef")
	oracleContractManifestPath = filepath.Join(oracleContractModPath, "oracle.manifest.json")
)

// GetTestContractState reads 2 pre-compiled contracts generated by
// TestGenerateHelperContracts, second of which is allowed to call the first.
func GetTestContractState(t *testing.T, pathToInternalContracts string, id1, id2 int32, sender2 util.Uint160) (*state.Contract, *state.Contract) {
	errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate")
	neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractNEFPath))
	require.NoError(t, err, fmt.Errorf("nef1: %w", errNotFound))
	ne, err := nef.FileFromBytes(neBytes)
	require.NoError(t, err)

	mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, helper1ContractManifestPath))
	require.NoError(t, err, fmt.Errorf("manifest1: %w", errNotFound))
	m := &manifest.Manifest{}
	err = json.Unmarshal(mBytes, m)
	require.NoError(t, err)

	cs1 := &state.Contract{
		ContractBase: state.ContractBase{
			NEF:      ne,
			Manifest: *m,
			ID:       id1,
		},
	}

	neBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractNEFPath))
	require.NoError(t, err, fmt.Errorf("nef2: %w", errNotFound))
	ne, err = nef.FileFromBytes(neBytes)
	require.NoError(t, err)

	mBytes, err = os.ReadFile(filepath.Join(pathToInternalContracts, helper2ContractManifestPath))
	require.NoError(t, err, fmt.Errorf("manifest2: %w", errNotFound))
	m = &manifest.Manifest{}
	err = json.Unmarshal(mBytes, m)
	require.NoError(t, err)

	// Retrieve hash of the first contract from the permissions of the second contract.
	require.Equal(t, 1, len(m.Permissions))
	require.Equal(t, manifest.PermissionHash, m.Permissions[0].Contract.Type)
	cs1.Hash = m.Permissions[0].Contract.Hash()

	cs2 := &state.Contract{
		ContractBase: state.ContractBase{
			NEF:      ne,
			Manifest: *m,
			ID:       id2,
			Hash:     state.CreateContractHash(sender2, ne.Checksum, m.Name),
		},
	}

	return cs1, cs2
}

// GetOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateHelperContracts and returns its state.
func GetOracleContractState(t *testing.T, pathToInternalContracts string, sender util.Uint160, id int32) *state.Contract {
	errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateHelperContracts to regenerate")

	neBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractNEFPath))
	require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
	ne, err := nef.FileFromBytes(neBytes)
	require.NoError(t, err)

	mBytes, err := os.ReadFile(filepath.Join(pathToInternalContracts, oracleContractManifestPath))
	require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
	m := &manifest.Manifest{}
	err = json.Unmarshal(mBytes, m)
	require.NoError(t, err)

	return &state.Contract{
		ContractBase: state.ContractBase{
			NEF:      ne,
			Hash:     state.CreateContractHash(sender, ne.Checksum, m.Name),
			Manifest: *m,
			ID:       id,
		},
	}
}