package native_test

import (
	"testing"

	"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
	"github.com/stretchr/testify/require"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/native/nativenames"
	"git.marketally.com/tutus-one/tutus-chain/pkg/core/state"
	"git.marketally.com/tutus-one/tutus-chain/pkg/tutustest"
	"git.marketally.com/tutus-one/tutus-chain/pkg/vm/stackitem"
)

func newAncoraClient(t *testing.T) *tutustest.ContractInvoker {
	return newNativeClient(t, nativenames.Ancora)
}

// TestAncora_GetConfig tests the getConfig method.
func TestAncora_GetConfig(t *testing.T) {
	c := newAncoraClient(t)

	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		arr, ok := stack[0].Value().([]stackitem.Item)
		require.True(t, ok, "expected array result")
		require.Equal(t, 7, len(arr)) // StateAnchorsConfig has 7 fields

		// Check default values
		defaultAlgo, _ := arr[0].TryInteger()
		require.Equal(t, int64(state.TreeAlgorithmSHA256), defaultAlgo.Int64())

		maxProofDepth, _ := arr[1].TryInteger()
		require.Equal(t, int64(32), maxProofDepth.Int64())

		defaultMaxUpdates, _ := arr[2].TryInteger()
		require.Equal(t, int64(10), defaultMaxUpdates.Int64())

		defaultCooldown, _ := arr[3].TryInteger()
		require.Equal(t, int64(1), defaultCooldown.Int64())

		maxHistory, _ := arr[4].TryInteger()
		require.Equal(t, int64(100), maxHistory.Int64())

		erasureGrace, _ := arr[5].TryInteger()
		require.Equal(t, int64(1000), erasureGrace.Int64())

		attestValid, _ := arr[6].TryInteger()
		require.Equal(t, int64(86400), attestValid.Int64())
	}, "getConfig")
}

// TestAncora_GetDataRoot_NonExistent tests getting a non-existent data root.
func TestAncora_GetDataRoot_NonExistent(t *testing.T) {
	c := newAncoraClient(t)

	// Non-existent root should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent root")
	}, "getDataRoot", uint64(999), uint32(state.DataTypeMedical))
}

// TestAncora_GetProvider_NonExistent tests getting a non-existent provider config.
func TestAncora_GetProvider_NonExistent(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent provider should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent provider")
	}, "getProvider", uint32(state.DataTypeMedical), acc.ScriptHash())
}

// TestAncora_GetErasureInfo_NonExistent tests getting non-existent erasure info.
func TestAncora_GetErasureInfo_NonExistent(t *testing.T) {
	c := newAncoraClient(t)

	// Non-existent erasure info should return null
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		require.Nil(t, stack[0].Value(), "expected null for non-existent erasure info")
	}, "getErasureInfo", uint64(999), uint32(state.DataTypeMedical))
}

// TestAncora_IsProviderActive_NonExistent tests checking non-existent provider.
func TestAncora_IsProviderActive_NonExistent(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)

	// Non-existent provider should return false
	c.Invoke(t, false, "isProviderActive", uint32(state.DataTypeMedical), acc.ScriptHash())
}

// TestAncora_RegisterProvider_NoCommittee tests that non-committee cannot register providers.
func TestAncora_RegisterProvider_NoCommittee(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	// registerProvider takes: dataType, provider, description, maxUpdatesPerBlock, updateCooldown
	randomInvoker.InvokeFail(t, "invalid committee signature",
		"registerProvider",
		uint32(state.DataTypeMedical),
		acc.ScriptHash(),
		"Test Healthcare Provider",
		uint32(10),
		uint32(1),
	)
}

// TestAncora_RegisterProvider_CommitteeSuccess tests committee can register providers.
func TestAncora_RegisterProvider_CommitteeSuccess(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	providerAcc := e.NewAccount(t)

	// Register a healthcare provider (c is already a CommitteeInvoker)
	c.Invoke(t, true,
		"registerProvider",
		uint32(state.DataTypeMedical),
		providerAcc.ScriptHash(),
		"Test Healthcare Provider",
		uint32(10),
		uint32(1),
	)

	// Verify provider is active
	c.Invoke(t, true, "isProviderActive", uint32(state.DataTypeMedical), providerAcc.ScriptHash())

	// Verify provider info
	c.InvokeAndCheck(t, func(t testing.TB, stack []stackitem.Item) {
		require.Equal(t, 1, len(stack))
		arr, ok := stack[0].Value().([]stackitem.Item)
		require.True(t, ok, "expected array result")
		require.Equal(t, 7, len(arr)) // ProviderConfig has 7 fields

		// Check dataType
		dataType, _ := arr[0].TryInteger()
		require.Equal(t, int64(state.DataTypeMedical), dataType.Int64())

		// Check active status
		active, _ := arr[4].TryBool()
		require.True(t, active)
	}, "getProvider", uint32(state.DataTypeMedical), providerAcc.ScriptHash())
}

// TestAncora_RevokeProvider_NoCommittee tests that non-committee cannot revoke providers.
func TestAncora_RevokeProvider_NoCommittee(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	randomInvoker.InvokeFail(t, "invalid committee signature",
		"revokeProvider",
		uint32(state.DataTypeMedical),
		acc.ScriptHash(),
	)
}

// TestAncora_UpdateDataRoot_VitaNotFound tests that updating root for non-existent Vita fails.
// The contract should fail when the Vita doesn't exist.
func TestAncora_UpdateDataRoot_VitaNotFound(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	root := hash.Sha256([]byte("test data")).BytesBE()

	// Should fail - when calling updateDataRoot with invalid Vita ID
	// The error could be "vita not found" or "unauthorized" depending on check order
	randomInvoker.InvokeFail(t, "",
		"updateDataRoot",
		uint64(999),
		uint32(state.DataTypeMedical),
		root,
		uint64(100),
		uint32(state.TreeAlgorithmSHA256),
		"1.0.0",
		[]byte{},
	)
}

// Note: TestAncora_VerifyProof tests would require complex array serialization
// that the test framework doesn't support directly. Proof verification is tested
// via cross-contract integration tests.

// TestAncora_RequestErasure_Unauthorized tests that erasure request requires Vita ownership.
// When Vita doesn't exist, OwnerOfInternal returns empty Uint160 and the caller
// fails the witness check, resulting in "unauthorized" error.
func TestAncora_RequestErasure_Unauthorized(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	// Should fail - caller is not the Vita owner (Vita doesn't exist)
	randomInvoker.InvokeFail(t, "unauthorized",
		"requestErasure",
		uint64(999),
		uint32(state.DataTypeMedical),
		"GDPR Art. 17",
	)
}

// TestAncora_ConfirmErasure_Unauthorized tests that unauthorized providers cannot confirm erasure.
func TestAncora_ConfirmErasure_Unauthorized(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	randomInvoker.InvokeFail(t, "unauthorized: caller is not authorized provider",
		"confirmErasure",
		uint64(1),
		uint32(state.DataTypeMedical),
	)
}

// TestAncora_DenyErasure_NoPending tests that denying non-existent erasure fails.
func TestAncora_DenyErasure_NoPending(t *testing.T) {
	c := newAncoraClient(t)
	e := c.Executor

	acc := e.NewAccount(t)
	randomInvoker := c.WithSigners(acc)

	randomInvoker.InvokeFail(t, "no pending erasure request",
		"denyErasure",
		uint64(1),
		uint32(state.DataTypeMedical),
		"Legal hold",
	)
}

// TestAncora_VerifyPortabilityAttestation_Invalid tests verification of invalid attestation.
func TestAncora_VerifyPortabilityAttestation_Invalid(t *testing.T) {
	c := newAncoraClient(t)

	// Invalid attestation should return false
	c.Invoke(t, false, "verifyPortabilityAttestation", []byte("invalid attestation"))
}

// TestAncora_DataTypes tests that all data types are valid.
func TestAncora_DataTypes(t *testing.T) {
	// Verify data type constants
	require.Equal(t, state.DataType(0), state.DataTypeMedical)
	require.Equal(t, state.DataType(1), state.DataTypeEducation)
	require.Equal(t, state.DataType(2), state.DataTypeInvestment)
	require.Equal(t, state.DataType(3), state.DataTypeDocuments)
	require.Equal(t, state.DataType(4), state.DataTypePresence) // VPP presence/heartbeat data
	require.Equal(t, state.DataType(5), state.DataTypeCustom)
}

// TestAncora_TreeAlgorithms tests that all tree algorithms are valid.
func TestAncora_TreeAlgorithms(t *testing.T) {
	// Verify tree algorithm constants
	require.Equal(t, state.TreeAlgorithm(0), state.TreeAlgorithmSHA256)
	require.Equal(t, state.TreeAlgorithm(1), state.TreeAlgorithmKeccak256)
	require.Equal(t, state.TreeAlgorithm(2), state.TreeAlgorithmPoseidon)
}

// TestAncora_ErasureStatuses tests that all erasure statuses are valid.
func TestAncora_ErasureStatuses(t *testing.T) {
	// Verify erasure status constants
	require.Equal(t, state.ErasureStatus(0), state.ErasurePending)
	require.Equal(t, state.ErasureStatus(1), state.ErasureConfirmed)
	require.Equal(t, state.ErasureStatus(2), state.ErasureDenied)
}
